samedi 17 janvier 2015

Pong game using C++ and SFML (source code explained)

Hi ! Today I'll be sharing the source of my first game using C++ and SFML. It didn't take much time to learn SFML (keep in mind that you must have a beginner/intermediate knowledge of C++) because it was VERY easy and straightforward.
I just want to note that this is not the best coding practice if you're going to make a big title (they need more classes to handle more complicated logic, code re-factoring, and memory management.) , but it will do it's purpose for this example since we're still learning the basics of game logic.
I'm also open for any improvements that you'll suggest.
Anyways, let's get started.We'll first start by defining our Ball class (what's a ball ? what are it's properties ? and what will it do ? etc...)
To understand this code you need basic knowledge of C++ and programming in general.

Ball.hpp
__________________________________________________________________________

#ifndef BALL_H
#define BALL_H
#include "paddle.hpp"
#include <stdio.h> 
#include <stdlib.h>  
#include <iostream>
#include <SFML/Graphics.hpp>
#include <math.h>

class Ball
{
    public:
        Ball();
        ~Ball();
        void GenerateVelocity();
        sf::Vector2f GetVelocity();
        void Move(float x, float y);
        void Draw(sf::RenderWindow* win);
        void SetPosition(int x, int y);
        sf::Vector2f GetPosition();
        sf::Vector2f GetSize();
        void Rest();
        void Bounce(int a, int b);
        bool DetectCollision(Paddle p);
        
    private:
        
        sf::CircleShape circleShape;
        sf::Vector2f velocity;
        int maxAxisSpeed;
};

#endif
_________________________________________________________________
For now, don't mind the includes we see the functions and the variables are pretty much explained by their id.
Okay now let's see the implementation of ball
Ball.cpp
__________________________________________________________________

#include "ball.hpp"

Ball::Ball()
{
    maxAxisSpeed = 12;
    circleShape.setRadius(10);
    circleShape.setFillColor(sf::Color::White);
    GenerateVelocity();
}

float RandomNumber(float Min, float Max)
{
    return ((float(rand()) / float(RAND_MAX)) * (Max - Min)) ;
}

void Ball::GenerateVelocity()
{
    float x, y;
    
    //generate horizontal velocity
    x = RandomNumber(1.5,7) - RandomNumber(1.5,7);
    
    //if horizontal velocity is too low renerate another one
    if(abs(x) < 1)
    {
        GenerateVelocity();
        return;
    }
    //generate vertical velocity
    y = RandomNumber(1.5,7) - RandomNumber(1.5,7) ;
    velocity = sf::Vector2f(x,y);
    float magnitude = sqrt(pow(velocity.x, 2) + pow(velocity.y,2));
    velocity.x = velocity.x/magnitude;
    velocity.y = velocity.y/magnitude;
    velocity.x = velocity.x * maxAxisSpeed;
    velocity.y = velocity.y * maxAxisSpeed;
}

Ball::~Ball()
{
    
}

void Ball::Move(float x, float y)
{
    circleShape.move(x,y);
}

void Ball::Draw(sf::RenderWindow* win)
{
    win->draw(circleShape);
}

void Ball::SetPosition(int x, int y)
{
    circleShape.setPosition(x,y);
}

sf::Vector2f Ball::GetSize()
{
    return sf::Vector2f(circleShape.getLocalBounds().width,circleShape.getLocalBounds().height);
}

sf::Vector2f Ball::GetVelocity()
{
    return velocity;
}

sf::Vector2f Ball::GetPosition()
{
    return circleShape.getPosition();
}

void Ball::Rest()
{
    GenerateVelocity();
}

void Ball::Bounce(int a, int b)
{
    velocity = sf::Vector2f(velocity.x * a,velocity.y * b);
}

bool Ball::DetectCollision(Paddle p)
{
    if(GetPosition().y > p.GetPosition().y + p.GetSize().y
    || GetPosition().y + GetSize().y < p.GetPosition().y
    || GetPosition().x > p.GetPosition().x + p.GetSize().x
    || GetPosition().x + GetSize().x < p.GetPosition().x)
    {
        return false;
    }
    else
    {    
        Bounce(-1,1);
    }
    
    
    return true;
}
________________________________________________________________

In the constructor Ball() we initialize our variable, ballShape parameters and we generate a velocity for the ball.
Let's get to our interesting function here, GenerateVelocity() it's goal is straightforward: generate a velocity for
the ball at first it uses the RandomNumber() function to initialize our vertical angle and make sure that it's high 
enough to make the ball go in a specific angle rather that just going in a straight horizontal path.The we do the 
same with our horizontal speed but we won't bother checking if it's a low value since we need an angle and we'll
be normalizing our velocity vector (take the unit vector from the velocity vector so we could have control over the
speed after getting a random direction) and multiply it by the desired 
speed.Now let's move on to DetectCollision() checks for collision (tada !) it uses the bounding box collision method and
if there is any collison with a paddle, then bounce using the Bounce() function which basically flips the x component of
the velocity vector of the ball.The move function uses the SFML built-in function to move the sprite.And the Draw function
takes a pointer to window (window class is not copyable) and draws the ball.
The setPosition() sets the position of the ball.
 getPosition() , getSize() and getVelocity() just return the properties of the ball class.

Now let's move on to the paddle.

Paddle.hpp
__________________________________________________________________________
#ifndef PADDLE_HPP
#define PADDLE_HPP
#include <SFML/Graphics.hpp>

class Paddle
{
    public:
        Paddle();
        ~Paddle();
        void Move(float y,unsigned int windowHeight,sf::Keyboard::Key keyUp,sf::Keyboard::Key keyDown);
        void Draw(sf::RenderWindow* win);
        void SetPosition(int x, int y);
        int GetScore();
        void IncrementScore();
        sf::Vector2f GetPosition();
        sf::Vector2f GetSize();
    private:
        sf::RectangleShape rectShape;
        int score;    
};

#endif
________________________________________________
Same thing with the paddle it has it's properties (plus the paddle here is treated as the player himself so it has a score and it's gonna
accept input)
Paddle.cpp
________________________________________________________________________
#include "paddle.hpp"

Paddle::Paddle()
{
    rectShape.setSize(sf::Vector2f(15,80));
    rectShape.setFillColor(sf::Color::White);
    score=0;
}

Paddle::~Paddle()
{
    
}

void Paddle::IncrementScore()
{
    score++;
}

int Paddle::GetScore()
{
    return score;
}

void Paddle::Move(float y,unsigned int windowHeight,sf::Keyboard::Key keyUp,sf::Keyboard::Key keyDown)
{
    if(sf::Keyboard::isKeyPressed(keyUp))
    {
        if(GetPosition().y <= 0 )
        {
            return;
        }
        rectShape.move(0,-y);
    }
    else if(sf::Keyboard::isKeyPressed(keyDown))
    {
        if(GetPosition().y + GetSize().y >= windowHeight)
        {
            return;
        }
        rectShape.move(0,y);        
    }
}

void Paddle::Draw(sf::RenderWindow* win)
{
    win->draw(rectShape);
}

void Paddle::SetPosition(int x, int y)
{
    rectShape.setPosition(x,y);
}

sf::Vector2f Paddle::GetSize()
{
    return sf::Vector2f(rectShape.getLocalBounds().width,rectShape.getLocalBounds().height);
}

sf::Vector2f Paddle::GetPosition()
{
    return rectShape.getPosition();
}
__________________________________________________________________________
The only interesting function we have here is Move() where it moves the paddle sprite and when it reaches
 the end of the screen it stops moving so the paddle won't go offscreen.

Now let's put it together in the main function.

Main.cpp
__________________________________________________________________________
#include "ball.hpp"
#include "paddle.hpp"
#include <string>
#include <sstream>

template <typename T>
//function to convert a non-string variable to a string.
std::string to_string(T value)
{
    std::ostringstream os ;
    os << value ;
    return os.str() ;
}

int main()
{
    //Creating a window
    sf::RenderWindow window(sf::VideoMode(600, 400), "Pong");
    window.setFramerateLimit(50);
    
    //instantiate the ball and sets the position to the middle of the screen 
    Ball ball;
    ball.SetPosition((window.getSize().x - ball.GetSize().x)/2,(window.getSize().y - ball.GetSize().y)/2 );
    
    //instantiate two players and put them in the desired pos
    Paddle p1, p2;
    p1.SetPosition(window.getSize().x - 40,  (window.getSize().y - p1.GetSize().y)/2);
    p2.SetPosition(40,  (window.getSize().y - p2.GetSize().y)/2);
    
    //Creating a text and setting it's font and parameters
    sf::Font font;
    sf::Text p1Score, p2Score;
    p1Score.setFont(font);
    p1Score.setColor(sf::Color::White);
    p1Score.setString("0");
    p1Score.setCharacterSize(54);
    p1Score.setStyle(sf::Text::Bold);
    p2Score = p1Score; // copy the shared properties
    p1Score.setPosition(30,30);
    p2Score.setPosition(window.getSize().x - 50,30);
    
    //load the font
    //note that this path is relative to the main.cpp itself
    if(!font.loadFromFile("Media/Fonts/fontfile.ttf"))
    {
        std::cout << "Failed to load font" << std::endl;
    }
    
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event)) //handle event input 
        {
            //Close window if x button is pressed
            if (event.type == sf::Event::Closed)
                window.close();
            
            //Reset
            if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::R)
            {
                ball.SetPosition((window.getSize().x - ball.GetSize().x)/2,(window.getSize().y - ball.GetSize().x)/2 );
                p1.SetPosition(window.getSize().x - 40,  (window.getSize().y - p1.GetSize().y)/2);
                p2.SetPosition(40,  (window.getSize().y - p2.GetSize().y)/2);
                ball.Rest();
            }
        }
        
        //move according to velocity
        ball.Move(ball.GetVelocity().x,ball.GetVelocity().y);
        
        //if ball moves out of screen
        if(ball.GetPosition().y <= 0)
        {
            ball.Bounce(1,-1);
        }

        if(ball.GetPosition().y + ball.GetSize().y  >= window.getSize().y)
        {
            ball.Bounce(1,-1);
        }
        
        //if player2 scores
        if(ball.GetPosition().x <= 40)
        {
            p2.IncrementScore();
            p2Score.setString(to_string(p2.GetScore()));
            ball.Rest();
            ball.SetPosition((window.getSize().x - ball.GetSize().x)/2,(window.getSize().y - ball.GetSize().x)/2 );
            p1.SetPosition(window.getSize().x - 40,  (window.getSize().y - p1.GetSize().y)/2);
            p2.SetPosition(40,  (window.getSize().y - p2.GetSize().y)/2);
        }
        
        //if player1 scores
        if(ball.GetPosition().x + ball.GetSize().x  >= window.getSize().x - 20)
        {
            p1.IncrementScore();
            p1Score.setString(to_string(p1.GetScore()));
            ball.Rest();
            ball.SetPosition((window.getSize().x - ball.GetSize().x)/2,(window.getSize().y - ball.GetSize().x)/2 );
            p1.SetPosition(window.getSize().x - 40,  (window.getSize().y - p1.GetSize().y)/2);
            p2.SetPosition(40,  (window.getSize().y - p2.GetSize().y)/2);
        }
        
        //Players movement
        p1.Move(8,window.getSize().y,sf::Keyboard::Up,sf::Keyboard::Down);
        p2.Move(8,window.getSize().y,sf::Keyboard::Z,sf::Keyboard::S);
        
        //Collision Detection
        ball.DetectCollision(p1);
        ball.DetectCollision(p2);
        
        //clearing back buffer (old image) and drawing the new image
        window.clear();
        window.draw(p1Score);
        window.draw(p2Score);
        p2.Draw(&window);
        p1.Draw(&window);
        ball.Draw(&window);
        window.display();
    }

    return 0;
}
__________________________________________________________________________
For the main.cpp file the explanation is commented with in the file because I'm a little lazy to split such a file haha.

I really hope this was somehow useful for you.
Thank you for your attention !

Note: -If you think that you can improve the code or the explanation in any kind, just leave a comment below.
-Sorry for the weird spacing that blogger is making, I don't know why it's there.

Aucun commentaire:

Enregistrer un commentaire