I've just finished making a pong clone written in C++ using SFML and would really appreciate some feedback and criticism on the game logic and/or coding style. One thing i really don't like is that my game states are just big switch staments. I've read about state machines and even tried implementing one but i just couldn't figure out how everything fits together, if anyone could help me with a state machine, that would be awesome.
The executable with DLL's and audio files.
[attachment=15505:Pong.rar]
Main.cpp
#include <Windows.h>
#include <SFML\Graphics.hpp>
#include "Game.h"
#include "Keyboard.h"
int WINAPI WinMain( HINSTANCE hinstance, HINSTANCE prevInstance, LPSTR lpCmdLine, int nCmdShow)
{
sf::RenderWindow window( sf::VideoMode(900, 700), "Pong", sf::Style::Close );
window.setVerticalSyncEnabled( true );
MyGame game( window );
Keyboard keyboard;
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
else if (event.type == sf::Event::KeyPressed)
keyboard.KeyDown( event.key.code );
else if (event.type == sf::Event::KeyReleased)
keyboard.KeyUp( event.key.code );
}
game.Update( keyboard );
keyboard.Update();
window.clear();
game.Draw();
window.display();
}
return 0;
}
Game.h
#ifndef GAME_H
#define GAME_H
#include <SFML\Graphics.hpp>
#include <SFML\Audio.hpp>
#include "Paddle.h"
#include "Ball.h"
#include "Scores.h"
enum ScreenState
{
SS_TITLE,
SS_INGAME
};
enum InGameState
{
GS_INIT,
GS_ONE,
GS_TWO,
GS_THREE,
GS_RUN,
GS_PAUSED,
GS_GAMEOVER
};
enum MenuState
{
MS_YES,
MS_NO
};
class MyGame
{
public:
MyGame( sf::RenderWindow& window );
void Update( Keyboard& keyboard );
void Draw();
private:
void InitText();
void InitSounds();
bool TestCollision( Paddle paddle, Ball ball );
void RestartGame();
void Reset();
private:
sf::RenderWindow& m_window;
sf::Clock m_clock;
sf::Clock m_eventClock;
float m_eventTime;
Paddle m_player1;
Paddle m_player2;
Ball m_ball;
Scores m_scores;
// States
ScreenState m_screenState;
InGameState m_inGameState;
MenuState m_menuState;
// Font and Text
sf::Font m_sqPixFont;
sf::Text txtTitle;
sf::Text txtPressEnt;
sf::Text txtThree;
sf::Text txtTwo;
sf::Text txtOne;
sf::Text txtWinner;
sf::Text txtPaused;
sf::Text txtQuitGame;
sf::Text txtPlayAgain;
sf::Text txtYes;
sf::Text txtNo;
sf::Color m_blue;
sf::Color m_yellow;
sf::Color m_grey;
// Sounds
sf::Music m_fearOfDark;
sf::SoundBuffer m_blip1Buffer;
sf::Sound m_blip1;
sf::SoundBuffer m_blip2Buffer;
sf::Sound m_blip2;
sf::SoundBuffer m_blopBuffer;
sf::Sound m_blop;
};
#endif
Game.cpp
#include "Game.h"
using std::to_string;
MyGame::MyGame( sf::RenderWindow& window )
: m_window( window ),
m_player1( 75.0f, window.getSize().x, window.getSize().y ),
m_player2( 815.0f, window.getSize().x, window.getSize().y ),
m_ball( window.getSize().x, window.getSize().y )
{
m_screenState = SS_TITLE;
m_inGameState = GS_INIT;
m_menuState = MS_YES;
m_blue = sf::Color( 0, 100, 200, 255 );
m_yellow = sf::Color( 255, 200, 0, 255 );
m_grey = sf::Color( 20, 20, 20, 255 );
InitText();
InitSounds();
m_scores.Init( m_sqPixFont, m_window );
m_fearOfDark.play();
}
void MyGame::Update( Keyboard& keyboard )
{
float frameTime = m_clock.getElapsedTime().asSeconds();
m_clock.restart();
m_eventTime = m_eventClock.getElapsedTime().asSeconds();
switch( m_screenState )
{
case SS_TITLE:
if( keyboard.KeyIsPressed( sf::Keyboard::Escape ) )
m_window.close();
if( keyboard.KeyIsPressed( sf::Keyboard::Return ) )
{
m_screenState = SS_INGAME;
m_fearOfDark.stop();
m_eventClock.restart();
}
break;
case SS_INGAME:
switch( m_inGameState )
{
case GS_INIT:
{
if( m_eventTime > 1.0f )
{
m_blip1.play();
m_inGameState = GS_THREE;
m_eventTime = 0;
m_eventClock.restart();
}
break;
}
case GS_THREE:
{
if( m_eventTime > 1.0f )
{
m_blip1.play();
m_inGameState = GS_TWO;
m_eventTime = 0;
m_eventClock.restart();
}
break;
}
case GS_TWO:
{
if( m_eventTime > 1.0f )
{
m_blip1.play();
m_inGameState = GS_ONE;
m_eventTime = 0;
m_eventClock.restart();
}
break;
}
case GS_ONE:
{
if( m_eventTime > 1.0f )
{
m_blip2.play();
m_inGameState = GS_RUN;
m_eventTime = 0;
m_eventClock.restart();
}
break;
}
case GS_RUN:
{
if( m_scores.Winner() )
{
m_inGameState = GS_GAMEOVER;
break;
}
if( keyboard.KeyIsPressed( sf::Keyboard::Escape ) ||
keyboard.KeyIsPressed( sf::Keyboard::P ) )
m_inGameState = GS_PAUSED;
// Player one controls
if( keyboard.KeyIsHeld( sf::Keyboard::W ) )
m_player1.MoveUp( frameTime );
if( keyboard.KeyIsHeld( sf::Keyboard::S ) )
m_player1.MoveDown( frameTime );
// Player two controls
if( keyboard.KeyIsHeld( sf::Keyboard::Up ) )
m_player2.MoveUp( frameTime );
if( keyboard.KeyIsHeld( sf::Keyboard::Down ) )
m_player2.MoveDown( frameTime );
// World collision
if( m_ball.GetPosition().x < 0 )
{
m_ball.SetXPosition( 0 );
if( m_ball.GetSpeed().x < 0 )
{
m_ball.BounceX();
m_ball.IncreaseSpeed();
m_scores.Player2Scored();
}
}
if( m_ball.GetPosition().x + m_ball.GetSize().x >= m_window.getSize().x )
{
m_ball.SetXPosition( m_window.getSize().x - m_ball.GetSize().x );
if( m_ball.GetSpeed().x > 0 )
{
m_ball.BounceX();
m_ball.IncreaseSpeed();
m_scores.Player1Scored();
}
}
if( m_ball.GetPosition().y < 0 )
{
m_ball.SetYPosition( 0 );
if( m_ball.GetSpeed().y < 0 )
{
m_ball.BounceY();
}
}
if( m_ball.GetPosition().y + m_ball.GetSize().y >= m_window.getSize().y )
{
m_ball.SetYPosition( m_window.getSize().y - m_ball.GetSize().y );
if( m_ball.GetSpeed().y > 0 )
{
m_ball.BounceY();
}
}
// Paddle collision
if( TestCollision( m_player1, m_ball ) )
{
if( m_ball.GetSpeed().x < 0 )
{
m_blop.play();
m_ball.BouncePaddle( m_player1 );
m_ball.IncreaseSpeed();
}
}
if( TestCollision( m_player2, m_ball ) )
{
if( m_ball.GetSpeed().x > 0 )
{
m_blop.play();
m_ball.BouncePaddle( m_player2 );
m_ball.IncreaseSpeed();
}
}
m_player1.Update();
m_player2.Update();
m_ball.Update( frameTime );
m_scores.Update();
break;
}
case GS_PAUSED:
{
if( keyboard.KeyIsPressed( sf::Keyboard::Escape ) ||
keyboard.KeyIsPressed( sf::Keyboard::P ) )
m_inGameState = GS_RUN;
if( keyboard.KeyIsPressed( sf::Keyboard::Left ) ||
keyboard.KeyIsPressed( sf::Keyboard::A ) )
m_menuState = MS_YES;
if( keyboard.KeyIsPressed( sf::Keyboard::Right ) ||
keyboard.KeyIsPressed( sf::Keyboard::D ) )
m_menuState = MS_NO;
if( keyboard.KeyIsPressed( sf::Keyboard::Return ) )
{
if( m_menuState == MS_YES )
Reset();
else
m_inGameState = GS_RUN;
}
if( m_menuState == MS_YES )
{
txtYes.setColor( m_blue );
txtNo.setColor( m_grey );
}
else if( m_menuState == MS_NO )
{
txtYes.setColor( m_grey );
txtNo.setColor( m_blue );
}
break;
}
case GS_GAMEOVER:
{
if( keyboard.KeyIsPressed( sf::Keyboard::Left ) ||
keyboard.KeyIsPressed( sf::Keyboard::A ) )
m_menuState = MS_YES;
if( keyboard.KeyIsPressed( sf::Keyboard::Right ) ||
keyboard.KeyIsPressed( sf::Keyboard::D ) )
m_menuState = MS_NO;
if( keyboard.KeyIsPressed( sf::Keyboard::Return ) )
{
if( m_menuState == MS_YES )
RestartGame();
if( m_menuState == MS_NO )
Reset();
}
if( m_menuState == MS_YES )
{
txtYes.setColor( m_blue );
txtNo.setColor( m_grey );
}
else if( m_menuState == MS_NO )
{
txtYes.setColor( m_grey );
txtNo.setColor( m_blue );
}
break;
}
}
break;
}
}
void MyGame::Draw()
{
m_eventTime = m_eventClock.getElapsedTime().asSeconds();
switch( m_screenState )
{
case SS_TITLE:
m_window.draw( txtTitle );
if( m_eventTime < 0.5f )
m_window.draw( txtPressEnt );
else if( m_eventTime > 1.0f )
m_eventClock.restart();
break;
case SS_INGAME:
{
switch( m_inGameState )
{
case GS_INIT:
break;
case GS_THREE:
m_window.draw( txtThree );
break;
case GS_TWO:
m_window.draw( txtTwo );
break;
case GS_ONE:
m_window.draw( txtOne );
break;
case GS_RUN:
break;
case GS_PAUSED:
m_window.draw( txtPaused );
m_window.draw( txtQuitGame );
m_window.draw( txtYes );
m_window.draw( txtNo );
break;
case GS_GAMEOVER:
m_window.draw( txtWinner );
m_window.draw( txtPlayAgain );
m_window.draw( txtYes );
m_window.draw( txtNo );
break;
}
m_scores.Draw( m_window );
m_player1.Draw( m_window );
m_player2.Draw( m_window );
m_ball.Draw( m_window );
break;
}
}
}
void MyGame::InitText()
{
int centerX = m_window.getSize().x / 2;
int centerY = m_window.getSize().y / 2;
m_sqPixFont.loadFromFile( "squarePixel7.ttf" );
txtTitle = sf::Text( "PONG", m_sqPixFont, 100);
txtTitle.setColor( m_blue );
txtTitle.setPosition( centerX - (txtTitle.getLocalBounds().width / 2), 100.0f );
txtPressEnt = sf::Text( "Press Enter", m_sqPixFont, 30 );
txtPressEnt.setColor( m_blue );
txtPressEnt.setPosition( centerX - (txtPressEnt.getLocalBounds().width / 2), 500 );
txtOne = sf::Text( "1", m_sqPixFont, 100 );
txtOne.setColor( m_blue );
txtOne.setPosition( centerX - (txtOne.getLocalBounds().width / 2) - 5, 400 );
txtTwo = sf::Text( "2", m_sqPixFont, 100 );
txtTwo.setColor( m_blue );
txtTwo.setPosition( centerX - (txtTwo.getLocalBounds().width / 2), 400 );
txtThree = sf::Text( "3", m_sqPixFont, 100 );
txtThree.setColor( m_blue );
txtThree.setPosition( centerX - (txtThree.getLocalBounds().width / 2), 400 );
txtWinner = sf::Text( "Winner", m_sqPixFont, 100 );
txtWinner.setColor( m_yellow );
txtWinner.setPosition( centerX - (txtWinner.getLocalBounds().width / 2), 100 );
txtPaused = sf::Text( "Paused", m_sqPixFont, 100 );
txtPaused.setColor( m_blue );
txtPaused.setPosition( centerX - (txtPaused.getLocalBounds().width / 2), 100 );
txtQuitGame = sf::Text( "Quit game?", m_sqPixFont, 30 );
txtQuitGame.setColor( m_blue );
txtQuitGame.setPosition( centerX - (txtQuitGame.getLocalBounds().width / 2), 450 );
txtPlayAgain = sf::Text( "Play Again?", m_sqPixFont, 30 );
txtPlayAgain.setColor( m_blue );
txtPlayAgain.setPosition( centerX - (txtPlayAgain.getLocalBounds().width / 2), 450 );
txtYes = sf::Text( "Yes", m_sqPixFont, 30 );
txtYes.setColor( m_grey );
txtYes.setPosition( centerX - txtYes.getLocalBounds().width - 50, 510);
txtNo = sf::Text( "No", m_sqPixFont, 30 );
txtNo.setColor( m_grey );
txtNo.setPosition( centerX + 50, 510);
}
void MyGame::InitSounds()
{
m_fearOfDark.openFromFile( "fearofdark.wav" );
m_blip1Buffer.loadFromFile( "Blip1.wav" );
m_blip1.setBuffer( m_blip1Buffer );
m_blip2Buffer.loadFromFile( "Blip2.wav" );
m_blip2.setBuffer( m_blip2Buffer );
m_blopBuffer.loadFromFile( "Blop.wav" );
m_blop.setBuffer( m_blopBuffer );
}
bool MyGame::TestCollision( Paddle paddle, Ball ball )
{
float padX = paddle.GetPosition().x;
float padY = paddle.GetPosition().y;
float padW = paddle.GetSize().x;
float padH = paddle.GetSize().y;
float ballX = ball.GetPosition().x;
float ballY = ball.GetPosition().y;
float ballW = ball.GetSize().x;
float ballH = ball.GetSize().y;
if( padX + padW < ballX || ballX + ballW < padX ) return false;
if( padY + padH < ballY || ballY + ballH < padY ) return false;
return true;
}
void MyGame::RestartGame()
{
m_inGameState = GS_INIT;
m_menuState = MS_YES;
m_clock.restart();
m_eventClock.restart();
m_player1.Reset();
m_player2.Reset();
m_ball.Reset();
m_scores.Reset();
}
void MyGame::Reset()
{
m_screenState = SS_TITLE;
RestartGame();
}
Paddle.h
#ifndef PADDLE_H
#define PADDLE_H
#include <SFML\Graphics.hpp>
class Paddle
{
public:
Paddle();
Paddle( float posX, int screenHeight, int screenWidth );
void Update();
void Draw( sf::RenderWindow& window );
void MoveUp( float frameTime );
void MoveDown( float frameTime );
void Reset();
sf::Vector2f GetPosition();
sf::Vector2f GetSize();
private:
int m_screenWidth, m_screenHeight;
sf::RectangleShape m_rectangle;
sf::Vector2f m_position;
int m_width, m_height;
float m_moveSpeed;
// Stored variables for reset.
float m_initX, m_initY;
};
#endif
Paddle.cpp
#include "Paddle.h"
#include "Keyboard.h"
Paddle::Paddle()
{}
Paddle::Paddle( float posX, int screenWidth, int screenHeight )
: m_screenWidth( screenWidth ),
m_screenHeight( screenHeight ),
m_width( 15 ),
m_height( 100 ),
m_moveSpeed( 500 )
{
m_initX = m_position.x = posX;
m_initY = m_position.y = ((float)screenHeight / 2.0f) - ((float)m_height / 2.0f);
m_rectangle = sf::RectangleShape( sf::Vector2f( (float)m_width, (float)m_height ) );
m_rectangle.setPosition( m_position.x, m_position.y );
m_rectangle.setFillColor( sf::Color( 0, 100, 200, 255 ) );
}
void Paddle::Update()
{
// Clamp to screen
if( m_position.y < 0 )
m_position.y = 0;
if( m_position.y + m_height >= m_screenHeight )
m_position.y = (float)m_screenHeight - (float)m_height;
m_rectangle.setPosition( m_position.x, m_position.y );
}
void Paddle::Draw( sf::RenderWindow& window )
{
window.draw( m_rectangle );
}
void Paddle::MoveUp( float frameTime )
{
m_position.y -= m_moveSpeed * frameTime;
}
void Paddle::MoveDown( float frameTime )
{
m_position.y += m_moveSpeed * frameTime;
}
sf::Vector2f Paddle::GetPosition()
{
return m_position;
}
sf::Vector2f Paddle::GetSize()
{
sf::Vector2f size;
size.x = (float)m_width;
size.y = (float)m_height;
return size;
}
void Paddle::Reset()
{
m_position.x = m_initX;
m_position.y = m_initY;
m_rectangle.setPosition( m_position.x, m_position.y );
}
Ball.h
#ifndef BALL_H
#define BALL_H
#include <SFML\Graphics.hpp>
#include <SFML\Audio.hpp>
#include "Paddle.h"
class Ball
{
public:
Ball( int screenWidth, int screenHeight );
void Update( float frameTime );
void Draw( sf::RenderWindow& window );
void BounceX();
void BounceY();
void BouncePaddle( Paddle paddle );
void SetXPosition( float x );
void SetYPosition( float y );
void IncreaseSpeed();
void Reset();
sf::Vector2f GetPosition();
sf::Vector2f GetSize();
sf::Vector2f GetSpeed();
private:
sf::RectangleShape m_rectangle;
sf::Vector2f m_position;
int m_width, m_height;
float m_speedX, m_speedY, m_maxYSpeed;
// Store variables for reset
float m_initX, m_initY;
float m_initYspeed;
};
#endif
Ball.cpp
#include "Ball.h"
#include <random>
#include <time.h>
Ball::Ball( int screenWidth, int screenHeight )
: m_maxYSpeed( 650 ),
m_initYspeed( 650 )
{
// Randomize the initial direction.
srand( (unsigned int)time(NULL) );
int xDirection = rand() % 2;
if( xDirection == 0 )
xDirection = -1;
m_speedX = xDirection * 600;
m_speedY = 0;
m_width = m_height = 10;
m_initX = m_position.x = (screenWidth / 2) - (m_width / 2);
m_initY = m_position.y = (screenHeight / 2) - (m_height / 2);
m_rectangle = sf::RectangleShape( sf::Vector2f( (float)m_width, (float)m_height ) );
m_rectangle.setPosition( m_position.x, m_position.y );
m_rectangle.setFillColor( sf::Color( 0, 100, 200, 255 ) );
}
void Ball::Update( float frameTime )
{
m_position.x += m_speedX * frameTime;
m_position.y += m_speedY * frameTime;
m_rectangle.setPosition( m_position.x, m_position.y );
}
void Ball::Draw( sf::RenderWindow& window )
{
window.draw( m_rectangle );
}
sf::Vector2f Ball::GetPosition()
{
return m_position;
}
sf::Vector2f Ball::GetSize()
{
sf::Vector2f size;
size.x = (float)m_width;
size.y = (float)m_height;
return size;
}
sf::Vector2f Ball::GetSpeed()
{
sf::Vector2f speed;
speed.x = m_speedX;
speed.y = m_speedY;
return speed;
}
void Ball::BounceX()
{
m_speedX *= -1;
}
void Ball::BounceY()
{
m_speedY *= -1;
}
void Ball::BouncePaddle( Paddle paddle )
{
BounceX();
float paddleY = paddle.GetPosition().y + (paddle.GetSize().y / 2.0f);
float ballY = m_position.y + (m_height / 2.0f);
m_speedY = (ballY - paddleY) / (paddle.GetSize().y / 2.0f) * m_maxYSpeed;
}
void Ball::SetXPosition( float x )
{
m_position.x = x;
}
void Ball::SetYPosition( float y )
{
m_position.y = y;
}
void Ball::IncreaseSpeed()
{
if( m_speedX < 0 )
m_speedX -= 1;
if( m_speedX > 0 )
m_speedX += 1;
m_maxYSpeed += 1;
}
void Ball::Reset()
{
m_position.x = m_initX;
m_position.y = m_initY;
m_maxYSpeed = m_initYspeed;
m_rectangle.setPosition( m_position.x, m_position.y );
int xDirection = rand() % 2;
if( xDirection == 0 )
xDirection = -1;
m_speedX = xDirection * 500;
m_speedY = 0;
}
Scores.h
#ifndef SCORES_H
#define SCORES_H
#include <SFML\Graphics.hpp>
class Scores
{
public:
Scores();
void Init( sf::Font& font, sf::RenderWindow& window );
void Update();
void Draw( sf::RenderWindow& window );
void Player1Scored();
void Player2Scored();
void SetMaxScore( int maxScore );
bool Winner();
void Reset();
private:
int m_player1Score, m_player2Score, m_maxScore;
sf::Text m_txtScore1, m_txtScore2;
sf::Color m_grey;
sf::Color m_yellow;
bool m_winner;
};
#endif
Scores.cpp
#include "Scores.h"
Scores::Scores()
{}
void Scores::Init( sf::Font& font, sf::RenderWindow& window )
{
m_yellow = sf::Color( 255, 200, 0, 255 );
m_grey = sf::Color( 20, 20, 20, 255 );
m_player1Score = 0;
m_player2Score = 0;
m_maxScore = 10;
m_winner = false;
int centerY = window.getSize().y / 2;
m_txtScore1 = sf::Text( "00", font, 100 );
m_txtScore1.setColor( m_grey );
m_txtScore1.setPosition( 350 - m_txtScore1.getLocalBounds().width, centerY - (m_txtScore1.getLocalBounds().height / 2) - m_txtScore1.getLocalBounds().top );
m_txtScore2 = sf::Text( "00", font, 100 );
m_txtScore2.setColor( m_grey );
m_txtScore2.setPosition( 550, centerY - (m_txtScore2.getLocalBounds().height / 2) - m_txtScore2.getLocalBounds().top );
}
void Scores::Update()
{
if( m_player1Score >= m_maxScore )
{
m_player1Score = m_maxScore;
m_txtScore1.setColor( m_yellow );
m_winner = true;
}
if( m_player2Score >= m_maxScore )
{
m_player2Score = m_maxScore;
m_txtScore2.setColor( m_yellow );
m_winner = true;
}
std::string score1 = std::to_string( (_Longlong)m_player1Score );
if( m_player1Score < 10 )
score1 = std::string( "0" ).append( score1 );
m_txtScore1.setString( score1 );
std::string score2 = std::to_string( (_Longlong)m_player2Score );
if( m_player2Score < 10 )
score2 = std::string( "0" ).append( score2 );
m_txtScore2.setString( score2 );
}
void Scores::Draw( sf::RenderWindow& window )
{
window.draw( m_txtScore1 );
window.draw( m_txtScore2 );
}
void Scores::Player1Scored()
{
m_player1Score += 1;
}
void Scores::Player2Scored()
{
m_player2Score += 1;
}
void Scores::SetMaxScore( int maxScore )
{
m_maxScore = maxScore;
}
bool Scores::Winner()
{
return m_winner;
}
void Scores::Reset()
{
m_player1Score = 0;
m_player2Score = 0;
m_winner = false;
m_txtScore1.setColor( m_grey );
m_txtScore1.setString( "00" );
m_txtScore2.setColor( m_grey );
m_txtScore2.setString( "00" );
}