This is a simple Pong game for 2 players.
Controls are ↑ and ↓ keys for the right player and W S for the left one.
There is a score for each player on top of the screen but with no win conditions so far.
- I know that my detection and handle of collisions are written poorly (you can see it in Ball.cpp). How can I improve it?
- Is it too bad that almost all of my destructors are empty?
- What can you say about architecture design? And I'm curious to hear your opinion on comments (I think I don't have enough of them but not sure what to do with it).
main.cpp
#include "Game.h"
int main(int argc, char* argv[])
{
Game pong;
return 0;
}
Game.h
#ifndef GAME_H
#define GAME_H
#include <SDL.h>
#include "Player.h"
#include "Ball.h"
#include "Score.h"
class Game
{
public:
Game();
~Game();
SDL_Renderer* getRenderer()
{
return this->_renderer;
}
void draw();
void update();
private:
SDL_Window* _window;
SDL_Renderer* _renderer;
void gameLoop();
bool _quitFlag;
Player player1, player2;
Ball ball;
Score score1, score2;
};
#endif
Game.cpp
#include "Game.h"
#include <iostream>
#include "Globals.h"
#include "Input.h"
Game::Game()
{
this->_window = NULL;
this->_renderer = NULL;
this->_quitFlag = false;
if (SDL_Init(SDL_INIT_EVERYTHING) < 0 || TTF_Init() < 0)
{
std::cout << "Couldn't Initialize. SDL_Eror :" << SDL_GetError();
}
else
{
SDL_CreateWindowAndRenderer(globals::SCREEN_WIDTH, globals::SCREEN_HEIGHT, NULL, &_window, &_renderer);
if (this->_window == NULL || this->_renderer == NULL)
{
std::cout << "Couldn't Create Window or Renderer. SDL_Error :" << SDL_GetError();
}
else
{
SDL_SetWindowTitle(this->_window, "Pong");
player1 = Player(globals::SCREEN_WIDTH * globals::PLAYER_GAP_PERCENT, (globals::SCREEN_HEIGHT - globals::PLAYER_HEIGHT) / 2);
player2 = Player(globals::SCREEN_WIDTH * (1 - globals::PLAYER_GAP_PERCENT) - globals::PLAYER_WIDTH, (globals::SCREEN_HEIGHT - globals::PLAYER_HEIGHT) / 2);
score1 = Score(globals::SCREEN_WIDTH * 0.25 , 50);
score2 = Score(globals::SCREEN_WIDTH * 0.75 , 50);
// if everything initialised fine - start game Loop
this->gameLoop();
}
}
}
Game::~Game()
{
}
void Game::draw()
{
// Clears the backsurface
SDL_SetRenderDrawColor(this->_renderer, 0, 0, 0, 255);
SDL_RenderClear(this->_renderer);
// Draw every object on backsurface:
// Draw central line
SDL_SetRenderDrawColor(this->_renderer, 255, 255, 255, 255);
SDL_RenderDrawLine(this->_renderer, globals::SCREEN_WIDTH / 2, 0, globals::SCREEN_WIDTH / 2, globals::SCREEN_HEIGHT);
// Draw Players
player1.draw(this->getRenderer());
player2.draw(this->getRenderer());
ball.draw(this->getRenderer());
score1.draw(this->getRenderer());
score2.draw(this->getRenderer());
//Switch renderer with backsurface
SDL_RenderPresent(this->_renderer);
}
void Game::update()
{
//update players
player1.update();
player2.update();
ball.update();
//check for collision
ball.collisionCheck(player1, player2);
// check if someone wins
if (ball.getX() < 0)
{
ball.resetBall();
score1.increment();
}
if (ball.getX() + ball.getW() > globals::SCREEN_WIDTH)
{
ball.resetBall();
score2.increment();
}
}
void Game::gameLoop()
{
SDL_Event event;
Input input;
while (!this->_quitFlag)
{
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
_quitFlag = true;
break;
}
else if (event.type == SDL_KEYDOWN)
{
input.ButtonPressed(event.key.keysym.sym);
}
else if (event.type == SDL_KEYUP)
{
input.ButtonReleased(event.key.keysym.sym);
}
}
// Controls
if (input.IsKeyHeld(SDLK_UP))
{
player2.move(0, - globals::PLAYER_SPEED);
}
if (input.IsKeyHeld(SDLK_DOWN))
{
player2.move(0, globals::PLAYER_SPEED);
}
if (input.IsKeyHeld(SDLK_w))
{
player1.move(0, -globals::PLAYER_SPEED);
}
if (input.IsKeyHeld(SDLK_s))
{
player1.move(0, globals::PLAYER_SPEED);
}
this->update();
this->draw();
}
}
Globals.h
#ifndef GLOBALS_H
#define GLOBALS_H
namespace globals
{
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int PLAYER_WIDTH = 10;
const int PLAYER_HEIGHT = 80;
const float PLAYER_SPEED = 0.08;
const int BALL_SIZE = 20;
const float BALL_SPEED = 0.05;
const float BALL_ACCELERATION = 1.05;
const float PLAYER_GAP_PERCENT = 0.05;
const int FPS = 60;
}
struct Vector2
{
float x, y;
Vector2()
{
this->x = 0;
this->y = 0;
}
};
#endif
Input.h
#ifndef INPUT_H
#define INPUT_H
#include <map>
#include <string>
#include <SDL.h>
class Input
{
public:
Input();
~Input();
bool IsKeyHeld(SDL_Keycode keycode);
// Fills map of pressed keys
void ButtonPressed(SDL_Keycode);
// Clears map of pressed keys
void ButtonReleased(SDL_Keycode);
private:
std::map <SDL_Keycode, bool> _keysPressed;
};
#endif
Input.cpp
#include "Input.h"
Input::Input()
{}
Input::~Input()
{}
void Input::ButtonPressed(SDL_Keycode keycode)
{
this->_keysPressed[keycode] = true;
}
void Input::ButtonReleased(SDL_Keycode keycode)
{
this->_keysPressed[keycode] = false;
}
bool Input::IsKeyHeld(SDL_Keycode keycode)
{
return this->_keysPressed[keycode];
}
GameObject.h
#ifndef GAMEOBJECT_H
#define GAMEOBJECT_H
#include <SDL.h>
class GameObject
{
public:
GameObject();
~GameObject();
GameObject(int corX, int corY);
int getX() { return this->_x; };
int getY() { return this->_y; };
int getH() { return this->_rect.h; };
int getW() { return this->_rect.w; };
// Fills rect of current object on the renderer.
void draw(SDL_Renderer* renderer);
// Increment _x and _y by _dx and _dy
// after that _dx = _dy = 0
void update();
// Increment _dx and _dy
void move(float dx, float dy);
protected:
float _x, _y;
float _dx,_dy;
SDL_Rect _rect;
};
#endif
GameObject.cpp
#include "GameObject.h"
#include "Globals.h"
GameObject::GameObject()
{
}
GameObject::GameObject(int corX, int corY)
{
this->_x = corX;
this->_y = corY;
this->_dx = 0;
this->_dy = 0;
}
GameObject::~GameObject()
{
}
void GameObject::draw(SDL_Renderer* renderer)
{
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderFillRect(renderer, &this->_rect);
}
void GameObject::update()
{
this->_x += this->_dx ;
this->_dx = 0;
this->_rect.x = this->_x;
this->_y += this->_dy ;
this->_dy = 0;
this->_rect.y = this->_y;
}
void GameObject::move(float dx, float dy)
{
this->_dx += dx;
this->_dy += dy;
}
Player.h
#ifndef PLAYER_H
#define PLAYER_H
#include "GameObject.h"
#include <SDL.h>
class Player: public GameObject
{
public:
Player();
Player(int corX, int corY);
~Player();
void move(float dx, float dy);
};
#endif
Player.cpp
#include "Player.h"
#include "Globals.h"
Player::Player()
{}
Player::Player( int corX, int corY):GameObject(corX,corY)
{
this->_rect.h = globals::PLAYER_HEIGHT;
this->_rect.w = globals::PLAYER_WIDTH;
this->_rect.x = corX;
this->_rect.y = corY;
}
Player::~Player()
{}
void Player::move(float dx, float dy)
{
// Player on the upper edge of the screen
if ((this->_y + this->_dy + dy) < 0)
{
this->_y = 0;
this->_dy = 0;
}
// Player on the bottom edge of the screen
else if ((this->_y + this->_dy + dy) > globals::SCREEN_HEIGHT - globals::PLAYER_HEIGHT)
{
this->_y = globals::SCREEN_HEIGHT - globals::PLAYER_HEIGHT;
this->_dy = 0;
}
//Player somewhere in between
else GameObject::move(dx, dy);
}
Ball.h
#ifndef BALL_H
#define BALL_H
#include "GameObject.h"
#include "Player.h"
#include "Globals.h"
#include <SDL.h>
class Ball: public GameObject
{
public:
Ball();
~Ball();
void update();
void collisionCheck(Player &player1, Player &player2);
void resetBall();
private:
Vector2 _speedVector;
void collision(Player &player);
float _ballSpeed;
};
#endif
Ball.cpp
#include "Ball.h"
#include "Globals.h"
#include <math.h>
#include <random>
#include <ctime>
Ball::Ball()
{
this->resetBall();
}
Ball::~Ball()
{
this->_x = 0;
this->_y = 0;
this->_dx = 0;
this->_dy = 0;
this->_ballSpeed = 0;
this->_speedVector = Vector2();
this->_rect = { 0,0 };
}
void Ball::resetBall()
{
this->_rect = { globals::SCREEN_WIDTH / 2 - globals::BALL_SIZE / 2, globals::SCREEN_HEIGHT / 2 - globals::BALL_SIZE / 2, globals::BALL_SIZE, globals::BALL_SIZE };
this->_x = globals::SCREEN_WIDTH / 2 - globals::BALL_SIZE / 2;
this->_y = globals::SCREEN_HEIGHT / 2 - globals::BALL_SIZE / 2;
this->_ballSpeed = globals::BALL_SPEED;
// find degree for ball
std::srand(std::time(0));
int randDegree = rand() % 70 + 1;
// (-1)^n - gives random sign
this->_speedVector.x = std::pow(-1, rand()) * this->_ballSpeed * std::cos(randDegree * std::_Pi / 180.0);
this->_speedVector.y = std::pow(-1, rand()) * this->_ballSpeed * std::sin(randDegree * std::_Pi / 180.0);
}
void Ball::update()
{
this->move(this->_speedVector.x, this->_speedVector.y);
GameObject::update();
}
void Ball::collisionCheck(Player & player1, Player & player2)
{
// Check collision with first player
if ( this->_x + this->getW() >= player1.getX() && this->_x <= player1.getX() + player1.getW() )
{
if (this->_y + this->getW() >= player1.getY() && this->_y <= player1.getY() + player1.getH())
{
if (this->_x + this->getW() < player1.getX() + player1.getW())
{
// vertical collision
this->_speedVector.y = -this->_speedVector.y;
}
else
{
this->move(player1.getX() + player1.getW() - this->_x, 0);
collision(player1);
}
}
}
// Check collision with second player
if (this->_x + this->getW() >= player2.getX() && this->_x <= player2.getX() + player2.getW())
{
if (this->_y + this->getH() >= player2.getY() && this->_y <= player2.getY() + player2.getH())
{
if (this->_x > player2.getX())
{
// vertical collision
this->_speedVector.y = -this->_speedVector.y;
}
else
{
this->move(-(this->_x + this->getW() - player2.getX()), 0);
collision(player2);
}
}
}
// Check collision with screen
if (this->_y < 0 || this ->_y + globals::BALL_SIZE > globals::SCREEN_HEIGHT)
{
this->_speedVector.y = -this->_speedVector.y;
}
}
void Ball::collision(Player &player)
{
this->_ballSpeed *= globals::BALL_ACCELERATION;
// degree = [0..1]
float degree = (this->_y - (player.getY() - globals::BALL_SIZE)) / (player.getH() + globals::BALL_SIZE);
if (degree <= 0.1)
degree = 0.1;
if (degree >= 0.9)
degree = 0.9;
degree = degree * std::_Pi;
if (std::signbit(this->_speedVector.x))
{
// _speedVector.x < 0
this->_speedVector.x = this->_ballSpeed * sin(degree);
this->_speedVector.y = - this->_ballSpeed * cos(degree);
}
else
{
this->_speedVector.x = - this->_ballSpeed * sin(degree);
this->_speedVector.y = - this->_ballSpeed * cos(degree);
}
}
Score.h
#ifndef SCORE_H
#define SCORE_H
#include <SDL.h>
#include <SDL_ttf.h>
class Score
{
public:
Score();
Score(int x, int y);
~Score();
void increment() { this->_score++; }
int getScore() { return _score; }
void draw(SDL_Renderer* renderer);
private:
SDL_Texture* _texture;
TTF_Font* _font;
int _score;
int _x, _y;
};
#endif
Score.cpp
#include "Score.h"
#include <string>
#include "Globals.h"
Score::Score()
{
}
Score::Score(int x, int y)
{
this->_score = 0;
this->_font = TTF_OpenFont("unifont.ttf", 32);
this->_x = x;
this->_y = y;
}
void Score::draw(SDL_Renderer* renderer)
{
std::string text = std::to_string(this->_score);
SDL_Color colour = { 255,255,255,255 };
SDL_Surface* textSurface = NULL;
textSurface = TTF_RenderText_Solid(this->_font, text.c_str(), colour);
if (textSurface != NULL)
{
this->_texture = SDL_CreateTextureFromSurface(renderer, textSurface);
SDL_Rect destRect = { this->_x, this->_y, textSurface->w, textSurface->h };
SDL_RenderCopy(renderer, this->_texture, NULL, &destRect);
}
SDL_FreeSurface(textSurface);
textSurface = NULL;
SDL_DestroyTexture(this->_texture);
}
Score::~Score()
{
}