2
\$\begingroup\$

Trying to make a little 2D engine I can play with. The end goal is a simple multiplayer PVP game just using shapes. I will make one version using ECS and one using OOP for learning. This is the shell of the ECS version using Pong as a demo. Before I jump into the user interface I am wondering what people smarter than me think. Specifically looking for criticism on my attempt to translate the OOP style of game states to ECS but I am also not very experienced so any advice will be valuable. The idea was to support replacing the current state (Main Menu -> Play) or overlaying a new state with the option to keep the previous state running (Begin Round) or disconnecting it's event listeners for a full pause (End Round).

#pragma once
#include "Vector_2.hpp"
#include <box2d/b2_body.h>


namespace Box2D {
    
    
class Body {
    
public:

    enum class Type {Static = b2_staticBody, Dynamic = b2_dynamicBody, Kinematic = b2_kinematicBody};

    Body(b2Body* body) 
    :   body {body} {};
    
    b2Fixture* create_fixture(const b2FixtureDef& fixture_definition) {
        return body->CreateFixture(&fixture_definition);
    };
    
    b2Fixture* create_fixture(const b2Shape& shape, const float& density) {
        return body->CreateFixture(&shape, density);
    };

    Type get_type() const {
        return static_cast <Type> (body->GetType());
    };
    
    void set_transform(const Vector_2 <float>& position, const float& angle = 0.f) {
        body->SetTransform({position.x, position.y}, angle);
    };
    
    Vector_2 <float> get_position() const {
        return body->GetPosition();
    };

    float get_angle() const {
        return body->GetAngle();
    };
    void set_linear_velocity(const Vector_2 <float>& velocity) {
        body->SetLinearVelocity({velocity.x, velocity.y});
    };

    void apply_force(const Vector_2 <float>& force, const Vector_2 <float>& point, bool wake = true) {
        body->ApplyForce({force.x, force.y}, {point.x, point.y}, wake);
    };
    
    void apply_force_center(const Vector_2 <float>& force, bool wake = true) {
        body->ApplyForceToCenter({force.x, force.y}, wake);
    };

    void apply_impulse(const Vector_2 <float>& impulse, const Vector_2 <float>& point, bool wake = true) {
        body->ApplyLinearImpulse({impulse.x, impulse.y}, {point.x, point.y}, wake);
    };
    
    void apply_impulse_center(const Vector_2 <float>& impulse, bool wake = true) {
        body->ApplyLinearImpulseToCenter({impulse.x, impulse.y}, wake);
    };  

    void set_angular_velocity(const float& velocity) {
        body->SetAngularVelocity(velocity);
    };
    
    void apply_angular_impulse(const float& impulse, bool wake = true) {
        body->ApplyAngularImpulse(impulse, wake);
    };
    
    void apply_torque(const float& torque, bool wake = true) {
        body->ApplyTorque(torque, wake);
    };
    
    b2Body* get() {
        return body;
    };

private:

    b2Body* body {nullptr};
};

    
};
#pragma once
#include "Vector_2.hpp"
#include "Box2D/Body.hpp"


namespace Box2D {
    
    

class Body_Builder {
    
public:


    Body_Builder& set_type(const Body::Type& type) {
        body_definition.type = static_cast <b2BodyType> (type);
        return *this;
    };
    
    Body_Builder& set_position(const Vector_2 <float>& position) {
        body_definition.position.Set(position.x, position.y);
        return *this;
    };
    
    Body_Builder& set_linear_velocity(const Vector_2 <float>& velocity) {
        body_definition.linearVelocity.Set(velocity.x, velocity.y);
        return *this;
    };
    
    Body_Builder& set_angle(const float& radians) {
        body_definition.angle = radians;
        return *this;
    };

    Body_Builder& set_angular_velocity(const float& velocity) {
        body_definition.angularVelocity = velocity;
        return *this;
    };
    
    Body_Builder& set_angular_damping(const float& damping) {
        body_definition.angularDamping = damping;
        return *this;
    };
    
    Body_Builder& set_gravity_scale(const float& scale) {
        body_definition.gravityScale = scale;
        return *this;
    };

    Body_Builder& set_allow_sleep(const bool& is_allowed_sleep) {
        body_definition.allowSleep = is_allowed_sleep;
        return *this;
    };

    Body_Builder& set_awake(const bool& is_awake) {
        body_definition.awake = is_awake;
        return *this;
    };

    Body_Builder& set_fixed_rotation(const bool& is_rotation_fixed) {
        body_definition.fixedRotation = is_rotation_fixed;
        return *this;
    };
    
    Body_Builder& set_bullet(const bool& is_bullet) {
        body_definition.bullet = is_bullet;
        return *this;
    };

    const b2BodyDef& build() const {
        return body_definition;
    };
    
private:

    b2BodyDef body_definition;
};


};
#pragma once
#include "Vector_2.hpp"
#include <box2d/b2_fixture.h>
#include <box2d/b2_shape.h>


namespace Box2D {
    
    

class Fixture_Builder {
    
public:


    Fixture_Builder& set_shape(const b2Shape& shape) {
        fixture_definition.shape = &shape;
        return *this;
    };

    Fixture_Builder& set_friction(const float& friction) {
        fixture_definition.friction = friction;
        return *this;
    };

    Fixture_Builder& set_restitution(const float& restitution) {
        fixture_definition.restitution = restitution;
        return *this;
    };

    Fixture_Builder& set_restitution_threshold(const float& restitution_threshold) {
        fixture_definition.restitutionThreshold = restitution_threshold;
        return *this;
    };

    Fixture_Builder& set_density(const float& density) {
        fixture_definition.density = density;
        return *this;
    };

    Fixture_Builder& set_frictions(const bool& is_sensor) {
        fixture_definition.isSensor = is_sensor;
        return *this;
    };
    
    Fixture_Builder& set_user_data(void* data) {
        fixture_definition.userData.pointer = reinterpret_cast <uintptr_t> (data);
        return *this;
    };

    const b2FixtureDef& build() const {
        return fixture_definition;
    };
    
private:

    b2FixtureDef fixture_definition;
};


};
#pragma once
#include "Time.hpp"
#include <box2d/b2_world.h>
#include <box2d/b2_body.h>
#include <box2d/b2_draw.h>


namespace Box2D {
    
    
    
class World {

public:

    b2Body* create_body(const b2BodyDef& body_definition) {
        return world.CreateBody(&body_definition);
    };
    
    void destroy_body(b2Body* body) {
        world.DestroyBody(body);
    };
    
    void set_contact_listener(b2ContactListener* listener) {
        world.SetContactListener(listener);
    };
    
    void set_debug_draw(b2Draw* debug_draw) {
        world.SetDebugDraw(debug_draw);
    };
    
    void draw() {
        world.DebugDraw();
    };
    
    void update(const Time::Duration& timestep) {
        world.Step(timestep.count(), 8, 4);
    };
    
    void clear_forces() {
        world.ClearForces();
    };

private:
    
    b2World world {{0.f, 0.f}};
    
};



};
#pragma once
#include "Vector_2.hpp"
#include <box2d/b2_circle_shape.h>
#include <box2d/b2_polygon_shape.h>
#include <box2d/b2_edge_shape.h>
#include <box2d/b2_chain_shape.h>
#include <box2d/b2_math.h>
#include <vector>


namespace Box2D {
namespace Shape {
    
    

inline b2CircleShape Circle(const float& radius, const Vector_2 <float>& position = {0.f, 0.f}) {
    b2CircleShape circle;
    circle.m_p = {position.x, position.y};
    circle.m_radius = radius;
    return circle;
};

inline b2PolygonShape Rectangle(const Vector_2 <float>& size, const Vector_2 <float>& center = {0.f, 0.f}, const float& angle = {0.f}) {
    b2PolygonShape rectangle;
    rectangle.SetAsBox(size.x / 2.f, size.y / 2.f, {center.x, center.y}, angle);
    return rectangle;
};

inline b2PolygonShape Polygon(const std::vector <b2Vec2>& points) {
    b2PolygonShape polygon;
    polygon.Set(&points[0], points.size());
    return polygon;
};

inline b2EdgeShape Edge_One_Sided(const b2Vec2& point_A, const b2Vec2& point_B, const b2Vec2& ghost_A, const b2Vec2& ghost_B) {
    b2EdgeShape edge;
    edge.SetOneSided(ghost_A, point_A, point_B, ghost_B);
    return edge;
};

inline b2EdgeShape Edge_Two_Sided(const b2Vec2& point_A, const b2Vec2& point_B) {
    b2EdgeShape edge;
    edge.SetTwoSided(point_A, point_B);
    return edge;
};

inline b2ChainShape Loop(const std::vector <b2Vec2>& points) {
    b2ChainShape loop;
    loop.CreateLoop(&points[0], points.size());
    return loop;
};

inline b2ChainShape Chain(const std::vector <b2Vec2>& points, const b2Vec2& ghost_A, const b2Vec2& ghost_B) {
    b2ChainShape chain;
    chain.CreateChain(&points[0], points.size(), ghost_A, ghost_B);
    return chain;
};



};};
#pragma once
#include "Vector_2.hpp"
#include "Box2D/World.hpp"
#include "SFML/Window.hpp"
#include "SFML/Transform.hpp"
#include <box2d/b2_draw.h>
#include <SFML/Graphics/Vertex.hpp>
#include <SFML/Graphics/Transformable.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/PrimitiveType.hpp>
#include <SFML/Graphics/ConvexShape.hpp>
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <vector>


namespace Box2D {
    
    

class Debug_Draw : public b2Draw {
    
public:
    
    Debug_Draw(Box2D::World& world, SFML::Window& window) : window {window} {
        world.set_debug_draw(this);
        SetFlags(b2Draw::e_shapeBit | b2Draw::e_aabbBit | b2Draw::e_pairBit | b2Draw::e_centerOfMassBit);
        world_to_screen.set_scale({32.f, 32.f});
    };
    
    virtual void DrawPolygon(const b2Vec2* vertices, int vertex_count, const b2Color& color) {
        (void) color;
        sf::ConvexShape polygon {static_cast <size_t> (vertex_count)};
        polygon.setFillColor({0, 0, 0, 0});
        polygon.setOutlineColor({255, 0, 0, 255});
        polygon.setOutlineThickness(1.f / 32.f);
        for (int i {0}; i < vertex_count; i++) {
            polygon.setPoint(i, {vertices[i].x, vertices[i].y});
        };
        window.draw(polygon, world_to_screen.get());
    };

    virtual void DrawSolidPolygon(const b2Vec2* vertices, int vertex_count, const b2Color& color) {
        (void) color;
        sf::ConvexShape polygon {static_cast <size_t> (vertex_count)};
        polygon.setFillColor({150, 0, 0, 150});
        for (int i {0}; i < vertex_count; i++) {
            polygon.setPoint(i, {vertices[i].x, vertices[i].y});
        };
        window.draw(polygon, world_to_screen.get());
    };

    virtual void DrawCircle(const b2Vec2& center, float radius, const b2Color& color) {
        (void) color;
        sf::CircleShape circle {radius};
        circle.setOrigin(radius, radius);
        circle.setPosition(center.x, center.y);
        circle.setFillColor({0, 0, 0, 0});
        circle.setOutlineColor({255, 0, 0, 255});
        circle.setOutlineThickness(1.f / 32.f);
        window.draw(circle, world_to_screen.get());
    };

    virtual void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color) {
        (void) color; (void) axis;
        sf::CircleShape circle {radius};
        circle.setOrigin(radius, radius);
        circle.setPosition(center.x, center.y);
        circle.setFillColor({150, 0, 0, 150});
        window.draw(circle, world_to_screen.get());
    };
    
    virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) {
        (void) color;
        std::vector <sf::Vertex> line;
        line.push_back({{p1.x, p1.y}, {0, 255, 0, 255}});
        line.push_back({{p2.x, p2.y}, {0, 255, 0, 255}});
        window.draw(&line[0], 2, sf::Lines, world_to_screen.get());
    };

    virtual void DrawTransform(const b2Transform& transform) {
        float line_length = 0.4;

        Vector_2 <float> x_axis {transform.p + line_length * transform.q.GetXAxis()};
        sf::Vertex red_line[] = 
        {
            sf::Vertex({{transform.p.x, transform.p.y}, sf::Color::Yellow}),
            sf::Vertex({{x_axis.x, x_axis.y}, sf::Color::Yellow})
        };

        Vector_2 <float> y_axis = transform.p + line_length * transform.q.GetYAxis();
        sf::Vertex green_line[] = 
        {
            sf::Vertex({{transform.p.x, transform.p.y}, sf::Color::Yellow}),
            sf::Vertex({{y_axis.x, y_axis.y}, sf::Color::Yellow})
        };

        window.draw(red_line, 2, sf::Lines, world_to_screen.get());
        window.draw(green_line, 2, sf::Lines, world_to_screen.get());
    };

    virtual void DrawPoint(const b2Vec2& p, float size, const b2Color& color) {
        (void) size; (void) color;
        std::vector <sf::Vertex> point;
        point.push_back({{p.x, p.y}, {0, 0, 255, 255}});
        window.draw(&point[0], 1, sf::Points, world_to_screen.get());
    };

private:

    SFML::Window& window;
    SFML::Transform world_to_screen;

};



};
#pragma once
#include <entt/entt.hpp>


namespace EnTT {
    
    
    
using Entity = entt::entity;
    
    
    
};
#pragma once
#include <entt/signal/dispatcher.hpp>


namespace EnTT {
    
    
    
using Event_Dispatcher = entt::dispatcher;
    
    
    
};
#pragma once
#include <entt/entity/handle.hpp>


namespace EnTT {
    
    

using Handle = entt::handle;



};
#pragma once
#include <entt/core/hashed_string.hpp>


namespace EnTT {
    


using Hashed_String = entt::hashed_string;
    
    
    
};
#pragma once
#include <entt/entt.hpp>


namespace EnTT {
    
    
    
using Registry = entt::registry;
    
    
    
};
#pragma once
#include <entt/resource/cache.hpp>


namespace EnTT {
    
    
template <typename Resource>
using Resource_Cache = entt::resource_cache <Resource>;
    
    
    
};
#pragma once
#include <entt/resource/handle.hpp>


namespace EnTT {
    
    
template <typename Resource>
using Resource_Handle = entt::resource_handle <Resource>;
    
    
    
};
#pragma once
#include <entt/resource/loader.hpp>


namespace EnTT {
    
    
template <typename Loader, typename Resource>
using Resource_Loader = entt::resource_loader <Loader, Resource>;
    
    
    
};
#pragma once
#include <SFML/Window/Event.hpp>


namespace SFML {
    
    
    
using Event = sf::Event;
    
    
    
};
#pragma once
#include <SFML/Graphics/Font.hpp>


namespace SFML {
    
    
    
using Font = sf::Font;
    
    
    
};
#pragma once
#include <SFML/Graphics/Text.hpp>


namespace SFML {
    
    
    
using Text = sf::Text;

    
    
};
#pragma once
#include <SFML/Graphics/Transformable.hpp>


namespace SFML {
    
    
    
class Transform {
    
public:

    void set_origin(const Vector_2 <float>& origin) {
        sfml_transformable.setOrigin(origin.x, origin.y);
    };
    
    void set_position(const Vector_2 <float>& position) {
        sfml_transformable.setPosition(position.x, position.y);
    };
    
    void set_rotation(const float& degrees) {
        sfml_transformable.setRotation(degrees);
    };
    
    void set_scale(const Vector_2 <float>& scale) {
        sfml_transformable.setScale(scale.x, scale.y);
    };
    
    Vector_2 <float> get_position() const {
        return sfml_transformable.getPosition();
    };
    
    float get_rotation() const {
        return sfml_transformable.getRotation();
    };
    
    Vector_2 <float> get_scale() const {
        return sfml_transformable.getScale();
    };
    
    const sf::Transform& get() const {
        return sfml_transformable.getTransform();
    };

private:

    sf::Transformable sfml_transformable;
    
};
    
    
    
};
#pragma once
#include "Vector_2.hpp"
#include <SFML/Window/Event.hpp>
#include <SFML/Window/VideoMode.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/View.hpp>


namespace SFML {
    
    
    
class Window {
    
public:

    template <typename... Args>
    Window(Args&&... args)
    :   sfml_window {std::forward <Args> (args)...} {
        
            sfml_window.setKeyRepeatEnabled(false);         
    };
    
    Vector_2 <unsigned int> get_size() {
        return sfml_window.getSize();
    };  
    
    bool is_open() {
        return sfml_window.isOpen();
    };
    
    bool poll_event(sf::Event& event) {
        return sfml_window.pollEvent(event);
    };

    void clear(const sf::Color& color = {0, 0, 0, 255}) {
        sfml_window.clear(color);
    };
    
    template <typename... Args>
    void draw(Args&&... args) {
        sfml_window.draw(std::forward <Args> (args)...);
    };
    
    void display() {
        sfml_window.display();
    };  
    
    void close() {
        sfml_window.close();
    };

private:

    sf::RenderWindow sfml_window;
    sf::View view;
    
};
    
    
    
};
#pragma once
#include <initializer_list>
#include <stdexcept>
#include <math.h>

template <typename T>
class Vector_2 {
    
public:
    
    Vector_2()
    :   x {0.f}, y {0.f} {};

    Vector_2(const T& x, const T& y)
    :   x {x}, y {y} {};
    
    Vector_2(std::initializer_list <T> init_list) {
        if (init_list.size() != 2) {
            throw(std::length_error("Vector_2(std::initializer_list) : size != 2"));
        };
        
        x = *(init_list.begin());
        y = *(init_list.begin() + 1);
    };
    
    Vector_2 operator* (const T& scalar) const {
        return Vector_2 <T> {
            x * scalar,
            y * scalar,
        };
    };
    
    Vector_2& operator*= (const T& scalar) {
        x *= scalar;
        y *= scalar;
        return *this;
    };  
    
    Vector_2 operator/ (const T& scalar) const {
        return Vector_2 <T> {
            x / scalar,
            y / scalar,
        };
    };
    
    Vector_2& operator/= (const T& scalar) {
        x /= scalar;
        y /= scalar;
        return *this;
    };  
    
    Vector_2& operator+ (const T& scalar) {
        x += scalar;
        y += scalar;
        return *this;
    };      
    
    Vector_2& operator- (const T& scalar) {
        x -= scalar;
        y -= scalar;
        return *this;
    };  
    
    template <typename Vector_Type>
    Vector_2(const Vector_Type& vector_type)
    :   x {vector_type.x}, y {vector_type.y} {};
    
    
    template <typename Vector_Type>
    Vector_2& operator= (const Vector_Type& vector_type) {
        x = vector_type.x;
        y = vector_type.y;
        return *this;
    };
    
    template <typename Vector_Type>
    Vector_2 operator+ (const Vector_Type& vector_type) const {
        return Vector_2 <T> {
            x + vector_type.x,
            y + vector_type.y,
        };
    };
    
    template <typename Vector_Type>
    Vector_2& operator+= (const Vector_Type& vector_type) {
        x += vector_type.x;
        y += vector_type.y;
        return *this;
    };  

    template <typename Vector_Type>
    Vector_2 operator- (const Vector_Type& vector_type) const {
        return Vector_2 <T> {
            x - vector_type.x,
            y - vector_type.y,
        };
    };
    
    template <typename Vector_Type>
    Vector_2& operator-= (const Vector_Type& vector_type) {
        x -= vector_type.x;
        y -= vector_type.y;
        return *this;
    };  
    
    template <typename Vector_Type>
    Vector_2 operator* (const Vector_Type& vector_type) const {
        return Vector_2 <T> {
            x * vector_type.x,
            y * vector_type.y,
        };
    };
    
    template <typename Vector_Type>
    Vector_2& operator*= (const Vector_Type& vector_type) {
        x *= vector_type.x;
        y *= vector_type.y;
        return *this;
    };  
    
    template <typename Vector_Type>
    Vector_2 operator/ (const Vector_Type& vector_type) const {
        return Vector_2 <T> {
            x / vector_type.x,
            y / vector_type.y,
        };
    };
    
    template <typename Vector_Type>
    Vector_2& operator/= (const Vector_Type& vector_type) {
        x /= vector_type.x;
        y /= vector_type.y;
        return *this;
    };
    
    float magnitude() {
        return std::abs(std::sqrt(x * x + y * y));
    };
    
    Vector_2 unit() {
        return *this / magnitude();
    };

    T x, y;
    
private:

    
};
#pragma once
#include <iostream>
#include <chrono>
using namespace std::literals;


namespace Time {
    


using Clock = std::chrono::steady_clock;
using Duration = std::chrono::duration <double>;
using Point = std::chrono::time_point <Clock, Duration>;



};
#pragma once


struct User_Data {
    
    enum class Type {Player, Opponent, Ball, Border};
    
    User_Data(const User_Data::Type& type) 
    :   type {type} {};
    
    User_Data::Type type;
    
};
#pragma once
#include "Box2D/Body.hpp"


namespace Component {
namespace Physics {
    
    
    
using Body = Box2D::Body;
    
    
    
};};
#pragma once


namespace Component {
    
    
    
struct Score {
    
    unsigned int player {0}, opponent {0};
    
};
    
    
    
};
#pragma once
#include "User_Data.hpp"
#include "EnTT/Event_Dispatcher.hpp"
#include "Event/Scored.hpp"
#include <box2d/b2_world_callbacks.h>
#include <box2d/b2_contact.h>
#include <iostream>


namespace System {
    
    
    
class Collision : public b2ContactListener {

public:

    Collision(EnTT::Event_Dispatcher& event_dispatcher)
    :   event_dispatcher {event_dispatcher} {};

    void BeginContact (b2Contact* contact) {
        
        auto* fixture_A = contact->GetFixtureA();
        //auto* fixture_B = contact->GetFixtureB();
        auto index_A = contact->GetChildIndexA();
        //auto index_B = contact->GetChildIndexB();
        auto* user_data_a = reinterpret_cast <User_Data*> (fixture_A->GetUserData().pointer);
        //auto* user_data_b = reinterpret_cast <User_Data*> (fixture_B->GetUserData().pointer);
        
        if (user_data_a && user_data_a->type == User_Data::Type::Border) {
            
            if (index_A == 0) {
                
                event_dispatcher.enqueue <Event::Scored> (User_Data::Type::Opponent);
            
            };
            
            if (index_A == 2) {
                
                event_dispatcher.enqueue <Event::Scored> (User_Data::Type::Player);
            
            };
            
        };
        
    };

private:

    EnTT::Event_Dispatcher& event_dispatcher;
};



};
#pragma once
#include "Box2D/World.hpp"
#include "Box2D/Body.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Entity.hpp"
#include "Component/Physics/Body.hpp"


namespace System {
    
    
    
class Physics : public Box2D::World {
    
public:

    static constexpr float scale {32.f};
    
    void connect(EnTT::Registry& ecs) {
        
        ecs.on_destroy <Component::Physics::Body> ().connect <&System::Physics::on_destroy> (this);
    
    };
    
private:
    
    void on_destroy(EnTT::Registry& ecs, EnTT::Entity entity) {
        
        auto& body = ecs.get <Component::Physics::Body> (entity);
        destroy_body(body.get());
    
    };
    
};
    
    
    
};
#pragma once
#include "EnTT/Registry.hpp"
#include "EnTT/Entity.hpp"
#include "SFML/Window.hpp"
#include "SFML/Transform.hpp"
#include "Component/Graphics/Drawable.hpp"
#include "Component/Physics/Body.hpp"


namespace System {
    
    
    
class Graphics {
    
    
public:

    Graphics(const float& physics_scale) {
        
        transform.set_scale({physics_scale, physics_scale});
        
    };
    
    void update_transforms_from_bodies(EnTT::Registry& ecs) {
        
        auto view = ecs.view <Component::Graphics::Drawable, Component::Physics::Body> ();
        for (auto entity : view) {
            
            auto [drawable, body] = view.get <Component::Graphics::Drawable, Component::Physics::Body> (entity);
            drawable.transform.set_position(body.get_position());
        
        };
    
    };

    void draw(EnTT::Registry& ecs, SFML::Window& window) {
        
        auto view = ecs.view <Component::Graphics::Drawable> ();
        for (auto entity : view) {
            
            auto& drawable = view.get <Component::Graphics::Drawable> (entity);
            window.draw(*drawable.pointer, transform.get() * drawable.transform.get());
        
        };
        
    };

private:
    
    SFML::Transform transform;
};
    
    
    
};
#pragma once
#include "EnTT/Resource_Loader.hpp"
#include "SFML/Font.hpp"
#include <memory>
#include <string>


namespace Resource {
namespace Font {
    
    
    
struct Loader : EnTT::Resource_Loader <Resource::Font::Loader, SFML::Font> {
    
    std::shared_ptr <SFML::Font> load(const std::string& file) const {
        auto font = std::make_shared <SFML::Font> ();
        font->loadFromFile(directory + file);
        return font;
    };
    
private:

    const std::string directory {"../src/Game/Resource/Font/"};
    
};
    
    
    
};};
#pragma once
#include "EnTT/Resource_Cache.hpp"
#include "SFML/Font.hpp"


namespace Resource {
namespace Font {
    
    
    
struct Cache : EnTT::Resource_Cache <SFML::Font> {};
    
    
    
};};
#pragma once
#include <SFML/Window/Keyboard.hpp>


namespace Event {
    
    
    
struct Key_Pressed {
    
    sf::Keyboard::Key key;
    
};
    
    
    
};
#pragma once
#include <SFML/Window/Keyboard.hpp>


namespace Event {
    
    
    
struct Key_Released {
    
    sf::Keyboard::Key key;
    
};
    
    
    
};
#pragma once
#include "State/Base.hpp"
#include <memory>


namespace Event {
    
    
    
struct Push_State {
    
    std::unique_ptr <State::Base> state;
    
};
    
    
    
};
#pragma once


namespace Event {
    
    
    
struct Pop_State {};
    
    
    
};
#pragma once


namespace Event {
    
    
    
struct Reset {};
    
    
    
};
#pragma once


namespace Event {
    
    
    
struct Launch_Ball {};
    
    
    
};
#pragma once
#include "User_Data.hpp"


namespace Event {
    
    
    
struct Scored {
    
    User_Data::Type type;
    
};
    
    
    
};
#pragma once
#include "Time.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Event_Dispatcher.hpp"
#include "SFML/Window.hpp"


namespace State {
    
    
class Machine;
    
    
class Base {
    
public:

    Base(State::Machine& state_machine) 
    :   state_machine {state_machine} {};

    
    virtual ~Base() {};
    virtual void connect_event_listeners(EnTT::Event_Dispatcher&) = 0;
    virtual void disconnect_event_listeners(EnTT::Event_Dispatcher&) = 0;
    virtual void create_entities(EnTT::Registry&) = 0;
    virtual void destroy_entities(EnTT::Registry&) = 0;
    virtual void update(const Time::Duration&) = 0;
    
protected:

    State::Machine& state_machine;
};
    
    
    
};
#pragma once
#include "State/Base.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Event_Dispatcher.hpp"
#include "System/Physics.hpp"
#include "Resource/Font/Cache.hpp"
#include "Event/Push_State.hpp"
#include "Event/Pop_State.hpp"
#include <vector>


namespace State {
    
    
    
class Machine {
    
public:

    EnTT::Registry& ecs;
    EnTT::Event_Dispatcher& event_dispatcher;
    Resource::Font::Cache& font_cache;
    System::Physics& physics_system;
    SFML::Window& window;

    Machine(EnTT::Registry& ecs, EnTT::Event_Dispatcher& event_dispatcher, Resource::Font::Cache& font_cache, System::Physics& physics_system, SFML::Window& window) 
    :   ecs {ecs}, event_dispatcher {event_dispatcher}, font_cache {font_cache}, physics_system {physics_system}, window {window} {

            event_dispatcher.sink <Event::Push_State> ().connect <&State::Machine::push_state> (this);
            event_dispatcher.sink <Event::Pop_State> ().connect <&State::Machine::pop_state> (this);
    
    };
    
    void push_state(Event::Push_State& event) {
        
        states.push_back(std::move(event.state));
        states.back()->create_entities(ecs);
        states.back()->connect_event_listeners(event_dispatcher);
        
    };
    
    void pop_state(const Event::Pop_State& event) {
        
        (void) event;
        if (!states.empty()) {
            
            states.back()->disconnect_event_listeners(event_dispatcher);
            states.back()->destroy_entities(ecs);
            states.pop_back();
            
        };
        
    };
    
    void update_states(const Time::Duration& timestep) {
        
        for (auto& state : states) {
            
            state->update(timestep);
        
        };
        
    };

private:
    
    std::vector <std::unique_ptr <State::Base>> states;
    
};
    
    
    
};
#pragma once
#include "State/Base.hpp"
#include "State/Play.hpp"
#include "State/Begin_Round.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Handle.hpp"
#include "System/Physics.hpp"
#include "Event/Push_State.hpp"
#include "Event/Pop_State.hpp"


namespace State {
    
    
    
class Main_Menu : public State::Base {
    
public:

    Main_Menu(State::Machine& state_machine)
    :   Base {state_machine} {};

    void connect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        event_dispatcher.sink <Event::Key_Pressed> ().connect <&State::Main_Menu::enter_play> (this);
    
    };
    
    void disconnect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        event_dispatcher.sink <Event::Key_Pressed> ().disconnect <&State::Main_Menu::enter_play> (this);
    };
    
    void create_entities(EnTT::Registry& ecs) override {
        
        intro_text = {ecs, ecs.create()};
        auto font = state_machine.font_cache.handle(EnTT::Hashed_String {"OpenSans-Regular.ttf"});
        auto& drawable = intro_text.emplace <Component::Graphics::Drawable> (std::make_unique <sf::Text> ("MENU", font, 100));
        //reverse physics scale to avoid blurry text
        drawable.transform.set_scale({1.f / System::Physics::scale, 1.f / System::Physics::scale});
    
    };
    
    void destroy_entities(EnTT::Registry& ecs) override {
        
        (void) ecs;
        intro_text.destroy();
    
    };
    
    void update(const Time::Duration& timestep) override {
        
        (void) timestep;
    
    };
    
    void enter_play(const Event::Key_Pressed& event) {
        
        (void) event;
        state_machine.event_dispatcher.enqueue <Event::Pop_State> ();
        state_machine.event_dispatcher.enqueue <Event::Push_State> (std::make_unique <State::Play> (state_machine));
        state_machine.event_dispatcher.enqueue <Event::Push_State> (std::make_unique <State::Begin_Round> (state_machine));
    
    };
    
private:
    
    EnTT::Handle intro_text;
    
};
    
    
    
};
#pragma once
#include "State/Base.hpp"
#include "State/End_Round.hpp"
#include "State/Begin_Round.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Handle.hpp"
#include "SFML/Window.hpp"
#include "Box2D/Body.hpp"
#include "Box2D/Shape.hpp"
#include "Box2D/Body_Builder.hpp"
#include "Box2D/Fixture_Builder.hpp"
#include "System/Physics.hpp"
#include "Event/Push_State.hpp"
#include "Event/Pop_State.hpp"
#include "Event/Key_Pressed.hpp"
#include "Event/Key_Released.hpp"
#include "Event/Scored.hpp"
#include "Event/Reset.hpp"
#include "User_Data.hpp"
#include "Vector_2.hpp"
#include "Component/Score.hpp"
#include <iostream>


namespace State {
    
    
    
class Play : public State::Base {
    
public:

    Play(State::Machine& state_machine)
    :   Base {state_machine} {
        
    };

    void connect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        event_dispatcher.sink <Event::Key_Pressed> ().connect <&State::Play::on_key_pressed> (this);
        event_dispatcher.sink <Event::Scored> ().connect <&State::Play::on_scored> (this);
        event_dispatcher.sink <Event::Reset> ().connect <&State::Play::on_reset> (this);
        event_dispatcher.sink <Event::Launch_Ball> ().connect <&State::Play::on_launch_ball> (this);
    
    };
    
    void disconnect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        event_dispatcher.sink <Event::Key_Pressed> ().disconnect <&State::Play::on_key_pressed> (this);
        event_dispatcher.sink <Event::Scored> ().disconnect <&State::Play::on_scored> (this);
        event_dispatcher.sink <Event::Reset> ().disconnect <&State::Play::on_reset> (this);
        event_dispatcher.sink <Event::Launch_Ball> ().disconnect <&State::Play::on_launch_ball> (this);
    
    };
    
    void create_entities(EnTT::Registry& ecs) override {
        
        create_player(ecs);
        create_opponent(ecs);
        create_ball(ecs);
        create_border(ecs);
        create_scoreboard(ecs);
        
    };
    
    void destroy_entities(EnTT::Registry& ecs) override {

        (void) ecs;
        player.destroy();
        opponent.destroy();
        ball.destroy();
        border.destroy();
        scoreboard.destroy();
        
    };
    
    void update(const Time::Duration& timestep) override {
        
        (void) timestep;
    };
    
    void on_key_pressed(const Event::Key_Pressed& event) {
        
        auto& body = player.get <Component::Physics::Body> ();
        
        if (event.key == sf::Keyboard::W) {
            
            body.set_linear_velocity({0.f, -10.f});
            
        };
        
        if (event.key == sf::Keyboard::S) {
            
            body.set_linear_velocity({0.f, 10.f});
        
        };
        
    };
    
private:
    
    EnTT::Handle 
        player, 
        opponent,
        ball,
        border,
        scoreboard;
        
    User_Data 
        player_user_data {User_Data::Type::Player},
        opponent_user_data {User_Data::Type::Opponent},
        ball_user_data {User_Data::Type::Ball},
        border_user_data {User_Data::Type::Border};
        
    void create_player(EnTT::Registry& ecs) {

        player = {ecs, ecs.create()};
        auto window_size = state_machine.window.get_size();
        auto& body = player.emplace <Component::Physics::Body> (state_machine.physics_system.create_body(Box2D::Body_Builder{}
            .set_type(Box2D::Body::Type::Kinematic)
            .set_position({1.f, window_size.y / System::Physics::scale / 2.f})
            .build()));
        body.create_fixture(Box2D::Fixture_Builder{}
            .set_shape(Box2D::Shape::Polygon({
                {-0.125f, -1.25f},
                {0.125f, -1.25f},
                {0.2f, -0.25f},
                {0.2f, 0.25f},
                {0.125f, 1.25f},
                {-0.125f, 1.25f}}))
            .set_density(1.f)
            .set_friction(0.f)
            .set_user_data(&player_user_data)
            .build());
        auto& drawable = player.emplace <Component::Graphics::Drawable> (std::make_unique <sf::RectangleShape> (sf::Vector2f {0.25f, 2.5f}));
        drawable.transform.set_origin({0.125f, 1.25f});
        
    };
    
    void create_opponent(EnTT::Registry& ecs) {

        opponent = {ecs, ecs.create()};
        auto window_size = state_machine.window.get_size();
        auto& body = opponent.emplace <Component::Physics::Body> (state_machine.physics_system.create_body(Box2D::Body_Builder{}
            .set_type(Box2D::Body::Type::Kinematic)
            .set_position({window_size.x / System::Physics::scale - 1.f, window_size.y / System::Physics::scale / 2.f})
            .set_angle(180.f * (M_PI / 180.f))
            .build()));
        body.create_fixture(Box2D::Fixture_Builder{}
            .set_shape(Box2D::Shape::Polygon({
                {-0.125f, -1.25f},
                {0.125f, -1.25f},
                {0.2f, -0.25f},
                {0.2f, 0.25f},
                {0.125f, 1.25f},
                {-0.125f, 1.25f}}))
            .set_density(1.f)
            .set_friction(0.f)
            .set_user_data(&player_user_data)
            .build());
        auto& drawable = opponent.emplace <Component::Graphics::Drawable> (std::make_unique <sf::RectangleShape> (sf::Vector2f {0.25f, 2.5f}));
        drawable.transform.set_origin({0.125f, 1.25f});
    
    };
    
    void create_ball(EnTT::Registry& ecs) {
        
        ball = {ecs, ecs.create()};
        auto window_size = state_machine.window.get_size();
        auto& body = ball.emplace <Component::Physics::Body> (state_machine.physics_system.create_body(Box2D::Body_Builder{}
            .set_type(Box2D::Body::Type::Dynamic)
            .set_position({window_size.x / System::Physics::scale / 2.f, window_size.y / System::Physics::scale / 2.f})
            .build()));
        body.create_fixture(Box2D::Fixture_Builder{}
            .set_shape(Box2D::Shape::Circle(0.25f))
            .set_density(1.0f)
            .set_friction(0.f)
            .set_restitution(1.1f)
            .set_user_data(&ball_user_data)
            .build());
        auto& drawable = ball.emplace <Component::Graphics::Drawable> (std::make_unique <sf::CircleShape> (0.25f));
        drawable.transform.set_origin({0.25f, 0.25f});
    
    };
    
    void create_border(EnTT::Registry& ecs) {
        
        border = {ecs, ecs.create()};
        auto& body = border.emplace <Component::Physics::Body> (state_machine.physics_system.create_body(Box2D::Body_Builder{}
            .set_type(Box2D::Body::Type::Static)
            .build()));     
        auto window_size = state_machine.window.get_size();
        body.create_fixture(Box2D::Fixture_Builder{}
            .set_shape(Box2D::Shape::Loop({
                {0.f, 0.f},
                {0.f, window_size.y / System::Physics::scale},
                {window_size.x / System::Physics::scale, window_size.y / System::Physics::scale},
                {window_size.x / System::Physics::scale, 0.f}}))
            .set_density(1.f)
            .set_user_data(&border_user_data)
            .build());
    
    };
        
    void create_scoreboard(EnTT::Registry& ecs) {
        
        scoreboard = {ecs, ecs.create()};
        scoreboard.emplace <Component::Score> ();
        auto font = state_machine.font_cache.handle(EnTT::Hashed_String {"OpenSans-Regular.ttf"});
        auto& drawable = scoreboard.emplace <Component::Graphics::Drawable> (std::make_unique <sf::Text> ("0 | 0", font, 100));
        auto& text = static_cast <sf::Text&> (*drawable.pointer);
        text.setFillColor({255, 255, 255, 0});
        auto bounds = text.getLocalBounds();
        auto window_size = state_machine.window.get_size();
        drawable.transform.set_origin({bounds.width / 2.f, bounds.height / 2.f});
        drawable.transform.set_position({window_size.x / System::Physics::scale / 2.f, 3.f});
        //reverse physics scale to avoid blurry text
        drawable.transform.set_scale({1.f / System::Physics::scale, 1.f / System::Physics::scale});
    
    };
    
    void on_launch_ball(const Event::Launch_Ball& event) {
        
        (void) event;
        auto& ball_body = ball.get <Component::Physics::Body> ();
        ball_body.set_linear_velocity({10.f, 0.f});
    
    };
        
    void on_scored(const Event::Scored& event) {
        
        auto& player_body = player.get <Component::Physics::Body> ();
        player_body.set_linear_velocity({0.f, 0.f});
        auto& opponent_body = opponent.get <Component::Physics::Body> ();
        opponent_body.set_linear_velocity({0.f, 0.f});
        auto& ball_body = ball.get <Component::Physics::Body> ();
        ball_body.set_linear_velocity({0.f, 0.f});
        auto& score = scoreboard.get <Component::Score> ();
        auto& text = static_cast <sf::Text&> (*scoreboard.get <Component::Graphics::Drawable> ().pointer);
        
        if (event.type == User_Data::Type::Player) {
            
            score.player++;
        
        };
        
        if (event.type == User_Data::Type::Opponent) {
            
            score.opponent++;
        
        };
        
        text.setString(std::to_string(score.player) + " | " + std::to_string(score.opponent));
        state_machine.event_dispatcher.sink <Event::Key_Pressed> ().disconnect <&State::Play::on_key_pressed> (this);
        state_machine.event_dispatcher.enqueue <Event::Push_State> (std::make_unique <State::End_Round> (state_machine, text));
    };
    
    void on_reset(const Event::Reset& event) {
        
        (void) event;
        auto window_size = state_machine.window.get_size();
        auto& player_body = player.get <Component::Physics::Body> ();
        auto& opponent_body = opponent.get <Component::Physics::Body> ();
        auto& ball_body = ball.get <Component::Physics::Body> ();
        player_body.set_transform({1.f, window_size.y / System::Physics::scale / 2.f});
        player_body.set_linear_velocity({0.f, 0.f});
        opponent_body.set_transform({window_size.x / System::Physics::scale - 1.f, window_size.y / System::Physics::scale / 2.f}, opponent_body.get_angle());
        opponent_body.set_linear_velocity({0.f, 0.f});
        ball_body.set_transform({window_size.x / System::Physics::scale / 2.f, window_size.y / System::Physics::scale / 2.f});
        ball_body.set_linear_velocity({0.f, 0.f});
        ball_body.set_angular_velocity(0.f);
        auto& scoreboard_text = static_cast <sf::Text&> (*scoreboard.get <Component::Graphics::Drawable> ().pointer);
        scoreboard_text.setFillColor({255, 255, 255, 0});
        state_machine.event_dispatcher.sink <Event::Key_Pressed> ().connect <&State::Play::on_key_pressed> (this);
        state_machine.event_dispatcher.enqueue <Event::Push_State> (std::make_unique <State::Begin_Round> (state_machine));
    
    };
};
    
    
    
};
#pragma once
#include "Time.hpp"
#include "State/Base.hpp"
#include "State/Play.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Handle.hpp"
#include "System/Physics.hpp"
#include "Event/Push_State.hpp"
#include "Event/Pop_State.hpp"
#include "Event/Launch_Ball.hpp"
#include <string>
#include <iostream>

namespace State {
    
    
    
class Begin_Round : public State::Base {
    
public:

    Begin_Round(State::Machine& state_machine)
    :   Base {state_machine} {};

    void connect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        (void) event_dispatcher;
        
    };
    
    void disconnect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        (void) event_dispatcher;
        
    };
    
    void create_entities(EnTT::Registry& ecs) override {
    
        create_countdown(ecs);
        
    };
    
    void destroy_entities(EnTT::Registry& ecs) override {
        
        (void) ecs;
        countdown.destroy();
        
    };
    
    void update(const Time::Duration& timestep) override {
        
        if (!countdown_counter) {
            
            state_machine.event_dispatcher.enqueue <Event::Pop_State> ();           
            state_machine.event_dispatcher.enqueue <Event::Launch_Ball> ();         
            
        } else {
            
            duration += timestep;
            if (duration >= 1.0s) {
                
                duration = 0.0s;
                countdown_counter--;
                
                if (countdown_counter) { 
                
                    auto& text = static_cast <sf::Text&> (*countdown.get <Component::Graphics::Drawable> ().pointer);
                    text.setString(std::to_string(countdown_counter));
                    
                };
            };
        };
        
    };
    
    void create_countdown(EnTT::Registry& ecs) {
        
        countdown = {ecs, ecs.create()};
        auto font = state_machine.font_cache.handle(EnTT::Hashed_String {"OpenSans-Regular.ttf"});
        auto& drawable = countdown.emplace <Component::Graphics::Drawable> (std::make_unique <sf::Text> ("3", font, 100));
        auto& text = static_cast <sf::Text&> (*drawable.pointer);
        text.setFillColor({255, 255, 255, 255});
        auto bounds = text.getLocalBounds();
        auto window_size = state_machine.window.get_size();
        drawable.transform.set_origin({bounds.width / 2.f, bounds.height / 2.f});
        drawable.transform.set_position({window_size.x / System::Physics::scale / 2.f, 3.f});
        //reverse physics scale to avoid blurry text
        drawable.transform.set_scale({1.f / System::Physics::scale, 1.f / System::Physics::scale});
    };

    
private:

    int countdown_counter {3};
    EnTT::Handle countdown;
    Time::Duration duration {0.0s};
    
};
    
    
    
};
#pragma once
#include "Time.hpp"
#include "State/Base.hpp"
#include "State/Play.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Handle.hpp"
#include "System/Physics.hpp"
#include "Event/Push_State.hpp"
#include "Event/Pop_State.hpp"
#include "Event/Reset.hpp"

namespace State {
    
    
    
class End_Round : public State::Base {
    
public:

    End_Round(State::Machine& state_machine, sf::Text& scoreboard_text)
    :    Base {state_machine}, scoreboard_text {scoreboard_text} {};

    void connect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        (void) event_dispatcher;
        
    };
    
    void disconnect_event_listeners(EnTT::Event_Dispatcher& event_dispatcher) override {
        
        (void) event_dispatcher;
        
    };
    
    void create_entities(EnTT::Registry& ecs) override {
        
        (void) ecs;
    
    };
    
    void destroy_entities(EnTT::Registry& ecs) override {
        
        (void) ecs;
    };
    
    void update(const Time::Duration& timestep) override {
        
        current_duration += timestep;
        
        if (current_duration >= duration) {
            
            state_machine.event_dispatcher.enqueue <Event::Pop_State> ();
            state_machine.event_dispatcher.enqueue <Event::Reset> ();
            
        } else {
            
            unsigned char fade = static_cast <unsigned char> (255 - current_duration.count() * (255 / duration.count()));
            scoreboard_text.setFillColor({255, 255, 255, fade});
            
        };
    };

    
private:
    
    sf::Text& scoreboard_text;
    Time::Duration duration {3.0s}, current_duration {0.0s};
};
    
    
    
};
#pragma once
#include "Time.hpp"
#include "Vector_2.hpp"
#include "Event/Key_Pressed.hpp"
#include "Event/Key_Released.hpp"
#include "EnTT/Registry.hpp"
#include "EnTT/Entity.hpp"
#include "EnTT/Handle.hpp"
#include "EnTT/Event_Dispatcher.hpp"
#include "EnTT/Hashed_String.hpp"
#include "Box2D/World.hpp"
#include "Box2D/Debug_Draw.hpp"
#include "SFML/Window.hpp"
#include "System/Physics.hpp"
#include "System/Collision.hpp"
#include "System/Graphics.hpp"
#include "Resource/Font/Loader.hpp"
#include "Resource/Font/Cache.hpp"
#include "State/Machine.hpp"
#include "State/Intro.hpp"
#include "Event/Push_State.hpp"
#include "Event/Pop_State.hpp"

#include <iostream>


class Game {

    Time::Duration timestep, rollover_time, run_time;
    Time::Point end_loop;
    EnTT::Registry ecs;
    EnTT::Event_Dispatcher event_dispatcher;
    SFML::Window window {sf::VideoMode {1200, 800}, "Window"};
    System::Physics physics_system;
    System::Collision collision_system {event_dispatcher};
    System::Graphics graphics_system {System::Physics::scale};
    Box2D::Debug_Draw debug_draw {physics_system, window};
    Resource::Font::Cache font_cache;
    State::Machine state_machine {ecs, event_dispatcher, font_cache, physics_system, window};
    
    
public:

    Game() {
        
        initialize_time();
        physics_system.set_contact_listener(&collision_system);
        physics_system.connect(ecs);
        font_cache.load <Resource::Font::Loader> (EnTT::Hashed_String {"OpenSans-Regular.ttf"}, "OpenSans-Regular.ttf");
        
        event_dispatcher.enqueue <Event::Push_State> (std::make_unique <State::Intro> (state_machine));
        
    };

    void run() {
        
        while (window.is_open()) {
            time();
            input();
            before_physics();
            physics();
            after_physics();
            before_render();
            render();
        };
        
    };

private:

    void initialize_time() {
        
        timestep = 1.0s / 60;
        rollover_time = 0.0s;
        run_time = 0.0s;
        
    };
    
    void time() {
        
        Time::Point start_loop = Time::Clock::now();
        Time::Duration loop_time = start_loop - end_loop;
        if (loop_time > 0.25s) loop_time = 0.25s;
        end_loop = start_loop;
        rollover_time += loop_time;
        
    };
    
    void input() {      
        
        event_dispatcher.update();
        
        sf::Event event;
        while (window.poll_event(event)) {
            
            if (event.type == sf::Event::Closed) {
                window.close();
            };
            
            if (event.type == sf::Event::KeyPressed) {
                event_dispatcher.trigger <Event::Key_Pressed> (event.key.code);
            };
            
            if (event.type == sf::Event::KeyReleased) {
                event_dispatcher.trigger <Event::Key_Released> (event.key.code);
            };
                    
        };

        
    };  
    
    void before_physics() {};
    
    void physics() {
        
        while (rollover_time >= timestep) {
            
            run_time += timestep;
            rollover_time -= timestep;
            physics_system.update(timestep);
            physics_system.clear_forces();
            state_machine.update_states(timestep);
            
        };
    };
    
    void after_physics() {
        
        graphics_system.update_transforms_from_bodies(ecs);
        
    };
    
    void before_render() {
        const double alpha = rollover_time / timestep;
        (void) alpha;
    };
    
    void render() {
        window.clear();
        graphics_system.draw(ecs, window);
        //physics_system.draw();
        window.display();
    };
    
    
};
\$\endgroup\$
6
  • \$\begingroup\$ Hi. Welcome to Code Review! I'm having trouble understanding what this is actually supposed to do at the moment. It's a game engine that implements Pong but without a user interface? I don't know what that means. From your description, I would have expected to see some game engine classes and some Pong classes. Is this done on some level? What does it do? \$\endgroup\$
    – mdfst13
    Sep 21, 2021 at 6:38
  • \$\begingroup\$ Well, I read that I should only include small parts of the project instead of dumping the whole thing here. Am I doing it wrong? The complete code in the link produces a playable Pong game using SMFL to render, EnTT for components and systems and Box2D for the physics. It has an Intro and Main Menu state that just throw text on the screen. A Play state that creates the 2 paddles, pong ball and screen bounds. A Begin Round state that counts down from 3, allowing input from the Play state to continue. And an End Round state that shows the score and disables input from the Play state. \$\endgroup\$ Sep 21, 2021 at 7:30
  • \$\begingroup\$ "Well, I read that I should only include small parts of the project instead of dumping the whole thing here." Where? On Code Review, you should post any code that you want reviewed. That sounds more like the instructions for Stack Overflow, although you have way too much code for an SO question. Also, while you are welcome to post a link to your Pong implementation, it shouldn't be tagged or in the title if it's not included. \$\endgroup\$
    – mdfst13
    Sep 21, 2021 at 7:41
  • \$\begingroup\$ Whoops. I'll work on editing the rest of the project in. \$\endgroup\$ Sep 21, 2021 at 7:42
  • \$\begingroup\$ What does "ECS" mean in this context? \$\endgroup\$
    – JDługosz
    Sep 21, 2021 at 15:30

2 Answers 2

3
\$\begingroup\$

Avoid writing unnecessary wrappers

It seems that a lot of the code you are writing is wrappers around other types. For example, Box2D::Body is just a wrapper around a b2Body. While sometimes wrapping an existing type in a new one might be the right thing to do, I don't see it providing any value here, and instead only adding problems.

First of all, Box2D::Body wraps b2Body, but all its member functions still take parameters of pure Box2D types, like b2FixtureDef and b2Shape. If you really want to avoid using Box2D types directly, then you should wrap all of its types and ensure the user of your classes don't need to know about Box2D at all.

Then there are a few places where you did use your own types like Vector_2 instead of b2Vec2 as a parameter. But since Vector_2 is not wrapping a b2Vec2 but is a stand-alone implementation of a 2D vector, you now have to perform lots of manual conversions to and from those types.

By not storing a b2Body directly inside Box2D::Body but instead just storing a pointer, you've created two problems: one is that Box2D::Body is not owning the b2Body it wraps, and thus the calling code has to worry about ensuring the underlying b2Body is valid for the duration of the wrapping object. Second, the member functions of Box2D::Body now have to access the b2Body via pointer indirection, which might impact performance.

It's also a maintainability issue: if newer versions of the Box2D library change or add member functions to b2Body, you would have to update your wrapper class as well in order to make use of the changed or new features.

I strongly suggest you remove Box2D::Body and start using b2Body directly everywhere. The same goes for other wrappers, like Box2D::FixtureBuilder, Box2D::World, SFML::Window, and so on.

Once you commit to using Box2D directly, I also recommend you use b2Vec2 everywhere instead of using your own Vector_2 class. I think that after removing all these unnecessary wrappers, your code will have reduced to less than half its original size, and what's left will be more reviewable.

Use of namespaces

You declared namespaces with names like Box2D and EnTT. At first this was confusing me; why are you adding things to the namespaces of other libraries? However, Box2D doesn't namespace its types and the EnTT uses entt (all lowercase) as a namespace. I suggest you avoid causing potential confusion here and avoid using namespaces that sound like they belong to external libraries.

Some alternatives are to create a very distinct namespace for your own project, and put your types, classes and so on in that namespace. For example:

namespace Pong {
    using Entity = entt::entity;
    using Handle = entt::handle;
    ...

However, I still would avoid creating unnecessary type aliases. Some of them are even longer than the original, like this one:

using Event_Dispatcher = entt::dispatcher;

It doesn't save typing, and it hides what the original type is. Some type aliases can be useful, for example:

using Clock = std::chrono::steady_clock;

This saves typing, and makes it easy to change to a different clock by just changing a single line.

Use of States

Having a set of State objects take care of whether the main menu should be shown or decide what phase of the game you are in is an interesting inversion of control. It might have some advantages, but it is quite unusual, and it has some issues. For example, now your game loop is always the same regardless of the state; even if you are just in the main menu, you will update the time and the physics. What if you have a pause menu, and while paused no physics should be updated? This is not possible in your current implementation unless you modify Game::run() to somehow detect that the game is paused, adding coupling that I think you wanted to avoid. Although you could move some of that to State::Play::update().

I also wonder why you have State::Machine manages a vector of states itself. Why not just let EnTT manage those states for you?

Dealing with unused function arguments

I see you write (void) argument to avoid the compiler from producing warnings about unused arguments. However, a better way to do this is to not give the function argument a name, like so:

void update(const Time::Duration&) override {}

Stray semicolons

I see you write a semicolon after function definitions and even after some if-statements that are using curly braces. This is not necessary, and some compilers might also warn about them.

Don't fight the language

I knew this was going to be the response when I posted it all [...] The rest is for my brain.

It might make you more comfortable to have written wrappers that feel exactly like you want them to, and perhaps you also added those unnecessary semicolons because you feel it's more natural or symmetric, but it might be a problem for others that want to contribute to your code, as well as adding a lot of maintenance overhead for yourself. In the long run it might better if you just use the standard and external libraries as intended by their authors.

\$\endgroup\$
5
  • \$\begingroup\$ I knew this was going to be the response when I posted it all... How about those states though? The rest is for my brain. \$\endgroup\$ Sep 21, 2021 at 15:34
  • 1
    \$\begingroup\$ "less than halve its original size" should be "half" \$\endgroup\$
    – JDługosz
    Sep 21, 2021 at 15:50
  • \$\begingroup\$ Good point about the physics always running even if there are no bodies. AND I'LL FIGHT THIS LANGUAGE 'TIL I DIE! \$\endgroup\$ Sep 21, 2021 at 16:25
  • \$\begingroup\$ Can I post slight changes to the code here to be reviewed or would that be better off in a new post? \$\endgroup\$ Sep 21, 2021 at 19:54
  • \$\begingroup\$ You should not change your code after you have gotten answers. Creating a new post is the right thing to do. \$\endgroup\$
    – G. Sliepen
    Sep 21, 2021 at 20:01
2
\$\begingroup\$

Just a quick tip I noticed:

const float& density
Pass floats by value. They are primitive types that not only fit in a register but might even have special registers used just for them. Passing as a const reference is slower and takes up more memory.

Why are you using float instead of double? Generally, only use float if you have large quantities of them and need to save memory.


Also, "disconnecting it's [sic] event listeners" When you proofread, always mentally expand "it's" to "it is" (or possibly "it has") and this mistake will stand out. Also, remember that "his/hers/its" is a matched set and none of them use an apostrophe.


virtual ~Base() {};
Don't write an empty body for a destructor. Use =default instead.


    void create_entities(EnTT::Registry& ecs) override {
        
        (void) ecs;
    
    };
    
    void destroy_entities(EnTT::Registry& ecs) override {
        
        (void) ecs;
    };

I really don't understand why multiple functions do nothing but name the ecs parameter in the body. That's not calling any function and as far as I know there's no way to make that do anything. Are you meaning to forward the function to that object or something?

Update: G. Sliepen points out that this is to prevent warnings about unused parameters. In my experience, parameters to override functions don't give such warnings anyway — you should look over your warning settings. See https://en.cppreference.com/w/cpp/language/attributes/maybe_unused for the proper way to silence such warnings and keep the name declared.

\$\endgroup\$
6
  • \$\begingroup\$ My man... are you correcting my grammar? \$\endgroup\$ Sep 21, 2021 at 15:41
  • \$\begingroup\$ Game development tends to use floats because GPUs (and graphics APIs) tend to use floats. And (void)foo; does nothing by intent - it's a way to prevent "unused variable" compiler warnings. \$\endgroup\$
    – user673679
    Sep 21, 2021 at 15:53
  • \$\begingroup\$ More of a homonym usage. The grammar would be correct with the correct word. \$\endgroup\$
    – JDługosz
    Sep 21, 2021 at 15:54
  • \$\begingroup\$ I like your style so I'm going to let it slide. All the namespace is to unify the syntax and prevent dyslexia's cousin from paying me a visit. PERSONAL PREFERENCE. Those states though... \$\endgroup\$ Sep 21, 2021 at 16:10
  • \$\begingroup\$ What's dyslexia's cousin exactly? If there is a good reason for what you are doing, then of course keep doing it. Although this post might be read by many others, so just think of our advice being for a larger group of people, and ignore those parts that don't apply to you. \$\endgroup\$
    – G. Sliepen
    Sep 21, 2021 at 16:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.