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();
};
};