Overall Observations
If I were a teacher I would give you an A+ for effort and about a B- for implementation.
From a design point of view try to separate the logic of the game as much as possible from the display of the game. Real gaming companies will do this to be able to distribute the same game for multiple platforms. This would also allow the use of the same game core using different graphic packages. While I doubt that Model View Control (MVC) or Model View View Model (MVVM) are the exact design patterns that games are built on, it is the kind of concept you want to use.
When you design object oriented programs you want to try to follow the SOLID design principles. SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable. This will help you design your objects and classes better.
- The Single Responsibility Principle - A class should only have a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class.
- The Open–closed Principle - states software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
- The Liskov Substitution Principle - Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
- The Interface segregation principle - states that no client should be forced to depend on methods it does not use.
- The Dependency Inversion Principle - is a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details.
Class Declaration Organization
You will very rarely be the only person working on a project in the industry, one or more people may be implementing the logic of the program and one or more other people may be implementing the display of the program. Over time a programming convention has emerged that public properties and methods should be at the top of the class declaration so that the programmers working with you can easily find them. This is generally the organization within the implementation of the class as well.
Reduce the Includes Within the Header Files
Only include header files that are necessary for compilation to a header file, include the other header files in the C++ source file as necessary for compilation. There are multiple reasons for this, one is that a basic premise of object oriented design is encapsulation which means that the internals of the class are protected. Another reason for reducing the included files within a header file is how include files are implemented in C and C++, the code in the include header is actually copied physically into a temporary version of the C++ source file being compiled. This means that the simple main program below all 7 lines of it will actually contain possibly more than a 1000 lines of code, most of which it doesn't need to compile because in addition to the 60 lines of code Game.h there are 14 includes most of which are non-trivial.
#include "Game.h"
int main(int argc, char* argv[])
{
Game game;
return game.execute();
}
Each Class Declaration Should be in Its Own Header File
The file GameObjects.h contains 3 class declarations and multiple struct declarations, there should be 3 header files instead, GameObjects.h that declares the GameObject base class, ball.h that includes GameObjects.h and declares the ball class and platform.h that includes GameObjects.h. The file Game.h should include ball.h and platform.h and not GameObjects.h. It might also be better if you could find a way not to include those headers in Game.h, one way that comes to mind is to use class ball; and class platform; at the top top of the Game.h file, then the compiler knows that those are pointers to a class without knowing the details of the class. The ball.h file and the platform.h file can then be included prior to Game.h in Game.cpp.
I've modified CollisionDetection.h and CollisionDetection.cpp to demonstrate what I mean:
** CollisionDetection.h **
#pragma once
struct Circle;
struct SDL_Rect;
class CollisionDetection
{
public:
static int square_of_distance(int x1, int y1, int x2, int y2);
static void detectCollision(const Circle& item1, const SDL_Rect& item2, int& collisionX, int& collisionY);
};
** CollisionDetection.h **
#include <cmath>
#include "GameObjects.h"
#include "CollisionDetection.h"
#include <iostream>
// Checks if circle and rectangle have collided. Returns 2 ints representing where on x and y they collided. Both will be -1, -1 if no collision.
void CollisionDetection::detectCollision(const Circle& circle, const SDL_Rect& rectangle, int& collision_x, int& collision_y) {
collision_x = -1;
collision_y = -1;
int rectCollidePointY = 0;
int rectCollidePointX = 0;
// Check where on the y axis the circle is in relation to the rectangle
if (circle.y > rectangle.y + rectangle.h) rectCollidePointY = rectangle.y + rectangle.h; // circle below rectangle
else if (circle.y < rectangle.y) rectCollidePointY = rectangle.y; // circle above rectangle
else rectCollidePointY = circle.y; // circle somewhere in the middle of rectangle in y axis
// Check where on the x axis the circle is in relation to the rectangle
if (circle.x > rectangle.x + rectangle.w) rectCollidePointX = rectangle.x + rectangle.w; // circle to the right of whole rectangle
else if (circle.x < rectangle.x) rectCollidePointX = rectangle.x; // circle to the left of whole rectangle
else rectCollidePointX = circle.x; // circle somewhere in the middle of rectangle in x axis
int d = square_of_distance(circle.x, circle.y, rectCollidePointX, rectCollidePointY);
if (d < pow(circle.r, 2)) {
collision_x = rectCollidePointX;
collision_y = rectCollidePointY;
return;
}
}
int CollisionDetection::square_of_distance(int x1, int y1, int x2, int y2) {
return static_cast<int>(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}