SFML 2 – Tutorial – Break Out!

July 19, 2012 4 comments


Tutorial 03 – Basic Game Structure – Break Out!


In this tutorial, we will cover refresh rate, a basic game structure, a custom game loop and the game of Break Out!.


How to get the refresh rate in frame per second ?


With the Clock class, we can query the time spent between two redraws with getElapsedTime().

One million divided by the number of micro seconds since the last redraw is the number of frames that can be drawn in a second at the current rate. For more precision, we can wait several frames and get an average of the elasped time per frame.

Time time=clock.getElapsedTime();
float fFps=1000000/time.asMicroseconds();
std::stringstream s;
s<<fFps;
fps.setString(s.str());
clock.restart();

With that in place, we can see that the previous pong example ran at more than a thousand frames per second. This is wasteful in resources, I wouldn’t want to make the GPU overheat just for pong. Especially since my monitor doesn’t refresh the screen at more than 60 times a second.

So we’ll limit our framerate with window.setFramerateLimit(unsigned int). It makes window.display sleep a bit. window.setVerticalSyncEnabled(bool) can be used too but it can be overridden by global graphic parameters.

window.setFramerateLimit(60);
window.setVerticalSyncEnabled(true);


How to use Clock to get a smoother animation ?


If there is a long time spent between two redisplays, it can cause jerkiness. Objects that should have a constant speed appear to slow down with the frame rate. We compensate by scaling all object speeds by the time spent since last redisplay.

ball.move(ballSpeed.x*time.asMilliseconds(),  ballSpeed.y*time.asMilliseconds());


Basic Game Structure:


We’ll use a Game class with only an init and an exec public functions, greatly simplifying our main to :

int main()
{
	Game game;
	if (!game.init())
		return EXIT_FAILURE;
	return game.exec();
}

The game class inits SFML, sets up the game objects, it can update various parts of the game and enters a custom game loop when exec is called. It processes the events and displays the game scene.


Custom Game loop:


We’ve seen that calling setFramerate will make window.display() to sleep if it is called more often than the frame rate. How can we take that time back for our game engine ? There are cases where we wish to use as much computing power as possible. Even in pong or in our next example, break out, collision detection is easier if the speed vector of the ball is small (if it is too big we can even miss a collision). Using a faster game loop will allow a finer grained simulation.

	int exec()
	{
		Clock renderClock, updateClock;
		while (window.isOpen())
		{
			time=renderClock.getElapsedTime();
			float fFps=1000000/time.asMicroseconds();
			std::stringstream s;
			s<<fFps;
			fps.setString(s.str());
			renderClock.restart();

			const Int64 frameTime = 1000000/FRAMES_PER_SECOND;
			Clock c;
			Time t=c.getElapsedTime();
			Int64 nextFrameTime=t.asMicroseconds()+frameTime;

			int loops = 0;
			while( t.asMicroseconds() < nextFrameTime && loops < MAX_FRAMESKIP)
			{
				processEvents();
				updateTime=updateClock.restart().asMilliseconds();
				update();
				t=c.getElapsedTime();
				loops++;
			}

			display();
		}

Now the game loop will call update up to ten times faster than display. We use updateTime to scale the game object speed accordingly.


How to add sounds ?


Sound files of various formats are loaded into a SoundBuffer with:

bool 	SoundBuffer::loadFromFile (const std::string &filename)

Then we’ll declare a Sound from any available SoundBuffer and play it. We can adjust its pitch and volume with setPitch and setVolume.

In the next example, we simulate the use of the keyboard as a piano:

#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>

using namespace sf;

void playSound(Sound & s, float pitch)
{
	float p=s.getPitch();
	s.setPitch(pitch);
	s.play();
}

int main()
{
    VideoMode videoMode(320,240);
    RenderWindow window(videoMode,"Piano");

    SoundBuffer soundBuffer1;
    if (!soundBuffer1.loadFromFile("blip.wav"))
        return EXIT_FAILURE;
    Sound blip(soundBuffer1);

	SoundBuffer soundBuffer2;
	if (!soundBuffer2.loadFromFile("blam.wav"))
        return EXIT_FAILURE;
    Sound blam(soundBuffer2);

	SoundBuffer soundBuffer3;
	if (!soundBuffer3.loadFromFile("blap.wav"))
        return EXIT_FAILURE;
    Sound blap(soundBuffer3);

	SoundBuffer soundBuffer4;
	if (!soundBuffer4.loadFromFile("blop.wav"))
        return EXIT_FAILURE;
    Sound blop(soundBuffer4);

	Font font;
	if (!font.loadFromFile("tomb.ttf"))
		return EXIT_FAILURE;

	Text info("Use the keys \n A Z E R \n Q S D F\n W X C V\n to play sounds.",font,20);
 	info.setPosition(20,30);
	info.setColor(Color::Blue);

    while (window.isOpen())
    {
		window.clear();
		window.draw(info);
		window.display();

        Event event;
        while (window.pollEvent(event))
        {
            if (event.type == Event::Closed)
                window.close();
			else
			if (event.type == Event::KeyPressed)
			{
				switch(event.key.code)
				{
				  case Keyboard::Q:
					  playSound(blip, 1.0);
					break;
				  case Keyboard::S:
					  playSound(blam, 1.0);
					break;
				  case Keyboard::D:
					  playSound(blap, 1.0);
					break;
				  case Keyboard::F:
					  playSound(blop, 1.0);
					break;

				  case Keyboard::W:
					  playSound(blip, 0.8);
					break;
				  case Keyboard::X:
					  playSound(blam, 0.8);
					break;
				  case Keyboard::C:
					  playSound(blap, 0.8);
					break;
				  case Keyboard::V:
					  playSound(blop, 0.8);
					break;

				  case Keyboard::A:
					  playSound(blip, 1.2);
					break;
				  case Keyboard::Z:
					  playSound(blam, 1.2);
					break;
				  case Keyboard::E:
					  playSound(blap, 1.2);
					break;
				  case Keyboard::R:
					  playSound(blop, 1.2);
					break;

				  case Keyboard::Escape:window.close();
					break;

				}
			}
        }

    }
    return EXIT_SUCCESS;
}


How to improve our rectangle collisions ?


Until now, we have only simulated collisions coming from one side.

We now want a rectangle that reflects the ball according to the side it is coming from.

Supposing the ball can be represented by a point, we use the angle of its speed vector seen from the center of the rectangle to determine which side it is coming from.

For instance, we can now correct the previous game of pong so that players only hit the ball if it is coming from the front side. Collisions coming from the rear can be ignored.

		// ball collides with player1
		if (intersects(ball,player1))
		{
			FloatRect p= player1.getGlobalBounds();
			FloatRect b= ball.getGlobalBounds();

			//let o be the center of p
			Vector2f o=Vector2f(p.left+p.width/2,p.top+p.height/2);
			//om: vector from o to the center of the ball
			Vector2f om=Vector2f(b.left+b.width/2-o.x,b.top+b.height/2-o.y);
			// let's scale om to square dimensions and act as if p is a square
			om.x/=p.width;
			om.y/=p.height;
			// reflect the ball according to the angle of om
			float angle=atan2(om.y,om.x);
			if ( abs(angle) <PI/2) //right - PI/4 is restrictive
			{
				ballSpeed.x= abs(ballSpeed.x);//in case of double contact
				ballSpeed.y= (b.top+b.height/2 - p.top - p.height/2) / 100;
				int u = p.left +  p.width - b.left;
				b.left = p.left +  p.width + u;
				ball.setPosition(b.left,b.top);
				//increase ball speed by 2%
				ballSpeed.x*=1.02f;
				ballSpeed.y*=1.02f;
				blip.play();
			}
		}

In addition with these changes, we will use the new game structure, the new game loop and sounds.

Let’s now review our Pong source, freshly refactored:

#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include <algorithm>
#include <math.h>
#include <sstream>

#define PI   3.14159265358979323846

using namespace sf;

bool intersects (const RectangleShape & rect1,const RectangleShape & rect2)
{
    FloatRect r1=rect1.getGlobalBounds();
    FloatRect r2=rect2.getGlobalBounds();
    return r1.intersects (r2);
}

float clamp (const float x, const float a, const float b)
{
    return std::min(std::max(a,x),b);
}

class Game
{
private:
    static const int FRAMES_PER_SECOND = 60;    
    static const int MAX_FRAMESKIP = 10;
    static const int width = 640;
    static const int height= 480;
    static const int borderSize= 12;
    static const int margin = 50;
    static const int moveDistance = 20;
    

    RenderWindow window;
    Font font;

    RectangleShape top;
    RectangleShape left;
    RectangleShape right;
    RectangleShape bottom;    

    RectangleShape ball;

    Vector2f ballSpeed;

    RectangleShape player1;    
    RectangleShape player2;    

    RectangleShape middleLine;

    Text title;
    Text start;
    Text won;
    Text lost;
    Text score;
    Text fps;

    SoundBuffer soundBuffer1;
    Sound blip;
    SoundBuffer soundBuffer2;
    Sound blam;    
    SoundBuffer soundBuffer3;
    Sound blap;
    SoundBuffer soundBuffer4;
    Sound blop;

    Time time;
    Int32 updateTime;

    unsigned int p1Score, p2Score;

    enum states {INTRO, PLAYING, P1WON, P1LOST};
    
    int gameState;
    
public:
    Game()
    {
    }

    bool init()
    {
        VideoMode videoMode(width, height);
        window.create(videoMode,"Pong Structured");
        window.setVerticalSyncEnabled(true);
        window.setFramerateLimit(FRAMES_PER_SECOND);

        if (!font.loadFromFile("tomb.ttf"))
            return false;  

        if (!soundBuffer1.loadFromFile("blip.wav"))
            return false;
        
        if (!soundBuffer2.loadFromFile("blam.wav"))
            return false;
        
        if (!soundBuffer3.loadFromFile("blap.wav"))
            return false;

        if (!soundBuffer4.loadFromFile("blop.wav"))
            return false;

        setup();
        return true;
    }

    int exec()
    {
        Clock renderClock, updateClock;
        while (window.isOpen())
        {
            time=renderClock.getElapsedTime();
            float fFps=1000000/time.asMicroseconds();
            std::stringstream s;
            s<<fFps<<" fps";
            fps.setString(s.str());
            renderClock.restart();
        
            const Int64 frameTime = 1000000/FRAMES_PER_SECOND;
            Clock c;
            Time t=c.getElapsedTime();
            Int64 nextFrameTime=t.asMicroseconds()+frameTime;

            int loops = 0;
            while( t.asMicroseconds() < nextFrameTime && loops < MAX_FRAMESKIP) 
            {
                processEvents();
                updateTime=updateClock.restart().asMilliseconds();
                update();
                t=c.getElapsedTime();
                loops++;
            }        
            
            display();
        }
        
        return EXIT_SUCCESS;
    }

private:

    void processEvents()
    {
        Event event;
        while (window.pollEvent(event))
        {
            if ( (event.type == Event::Closed) ||
            ((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();    
            else
            if ((event.type == Event::KeyPressed) && (gameState == INTRO))
                gameState=PLAYING;
        }
    }

    void update()
    {
        if (gameState!=PLAYING)
            return;

        updatePlayer1();
        updatePlayer2();
        checkCollisions();
        updateBall();

        // detect if game is over
        if (p1Score >=11 && p1Score >= p2Score +2)
            gameState=P1WON;
        if (p2Score >=11 && p2Score >= p1Score +2)
            gameState=P1LOST;
    }

    void setup()
    {
        top.setPosition(-2*borderSize, 0);
        top.setSize(Vector2f(width+4*borderSize, borderSize));

        left.setPosition(-borderSize*3, 0);
        left.setSize(Vector2f(borderSize, height));

        right.setPosition(width+2*borderSize, 0);
        right.setSize(Vector2f(borderSize, height));

        bottom.setPosition(-2*borderSize, height-borderSize);
        bottom.setSize(Vector2f(width+4*borderSize, borderSize));
            
        top.setFillColor(Color(100,100,100));
        top.setOutlineColor(Color::Blue);
        top.setOutlineThickness(3);

        left.setFillColor(Color(100,100,100));
        left.setOutlineColor(Color::Blue);
        left.setOutlineThickness(3);

        right.setFillColor(Color(100,100,100));
        right.setOutlineColor(Color::Blue);
        right.setOutlineThickness(3);

        bottom.setFillColor(Color(100,100,100));
        bottom.setOutlineColor(Color::Blue);
        bottom.setOutlineThickness(3);
        
        ball.setPosition(width/2, height/2);
        ball.setSize(Vector2f(20, 20));
        ball.setFillColor(Color::Yellow);
        ball.setOutlineColor(Color::Red);
        ball.setOutlineThickness(2);

        player1.setSize(Vector2f(borderSize, 90));
        player1.setPosition(margin-borderSize, height/2-25);
        player1.setFillColor(Color(0,122,245));
        player1.setOutlineColor(Color::Red);
        player1.setOutlineThickness(3);
        
        player2.setSize(Vector2f(borderSize, 90));
        player2.setPosition(width-margin, height/2-25);
        player2.setFillColor(Color(0,122,245));
        player2.setOutlineColor(Color::Red);
        player2.setOutlineThickness(3);
            
        middleLine.setFillColor(Color(100,100,100,30));
        middleLine.setOutlineColor(Color(0,0,100,30));
        middleLine.setOutlineThickness(2);
        middleLine.setPosition(width/2, 0);
        middleLine.setSize(Vector2f(0, height));

        title.setString("Pong SFML 2");
        title.setFont(font);
        title.setCharacterSize(50);
        title.setPosition(width/2-title.getGlobalBounds().width/2,100);
        title.setColor(Color::Blue); 
    
        start.setString("Press any key to start");
        start.setFont(font);
        start.setCharacterSize(30);
        start.setPosition(width/2-start.getGlobalBounds().width/2,400);
        start.setColor(Color::Red);

        won.setString("You have won this game.\n\n Congratulations !");
        won.setFont(font);
        won.setCharacterSize(20);
        won.setPosition(width/2-won.getGlobalBounds().width/2,height/2-won.getGlobalBounds().height/2);
        won.setColor(Color::Green);

        lost.setString("You have lost this game, \n better luck next time!");
        lost.setFont(font);
        lost.setCharacterSize(20);
        lost.setPosition(width/2-lost.getGlobalBounds().width/2,height/2-lost.getGlobalBounds().height/2);
        lost.setColor(Color::Red);

        score.setString("0   0");
        score.setFont(font);
        score.setCharacterSize(50);
        score.setPosition(width/2-score.getGlobalBounds().width/2,40);
        score.setColor(Color(0,0,100,50));

        fps.setString("0");
        fps.setFont(font);
        fps.setCharacterSize(30);
        fps.setPosition(fps.getGlobalBounds().width/2,40);
        fps.setColor(Color(52,0,100,50));

        
        blip=Sound(soundBuffer1);        
        blam=Sound(soundBuffer2);    
        blap=Sound(soundBuffer3);    
        blop=Sound(soundBuffer4);
        
        resetGame1();
        p1Score=0;
        p2Score=0;
        gameState=INTRO;

    }

    void display()
    {        
        window.clear(Color::White);
        switch(gameState)
        {
            case INTRO:
                window.draw(title);
                window.draw(start);
                break;
            case PLAYING:
                window.draw(middleLine);
                window.draw(left);
                window.draw(right);            
                window.draw(player1);    
                window.draw(player2);    
                window.draw(ball);    
                window.draw(score);    
                window.draw(top);
                window.draw(bottom);        
                window.draw(fps);    
                break;
            case P1WON:
                window.draw(won);
                break;
            case P1LOST:
                window.draw(lost);
                break;
        }
        window.display();
    }

    void updatePlayer1()
    {
        // move player 1 pad
        if (Keyboard::isKeyPressed(Keyboard::Up))
        {
            player1.move(0,-moveDistance*updateTime/50.0);
        }else
        if (Keyboard::isKeyPressed(Keyboard::Down))
        {
            player1.move(0,moveDistance*updateTime/50.0);
        }
    }

    void updatePlayer2()
    {
        // auto move player2 pad
        if (ball.getPosition().y < player2.getPosition().y)
            player2.move(0, -moveDistance*updateTime/40.0);
        else if(ball.getPosition().y+ball.getSize().y > player2.getPosition().y+player2.getSize().y)
            player2.move(0, moveDistance*updateTime/40.0);
    }

    void updateBall()
    {
        ball.move(ballSpeed.x*updateTime,ballSpeed.y*updateTime);
    }

    void checkCollisions()
    {
                        
        // block players pad inside the play area
        if ( intersects(player1,bottom) || intersects(player1,top) )
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=bottom.getGlobalBounds();
            Vector2f p=player1.getPosition();
            p.y=clamp(p.y,t.top+t.height+5,b.top-player1.getSize().y-5);
            player1.setPosition(p);
            blap.play();
        }
        if ( intersects(player2,bottom) || intersects(player2,top) )
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=bottom.getGlobalBounds();
            Vector2f p=player2.getPosition();
            p.y=clamp(p.y,t.top+t.height+5,b.top-player2.getSize().y-5);
            player2.setPosition(p);
            blap.play();
        }
        // ball collides with top and bottom
        if (intersects(ball,top))
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ballSpeed.y*= -1;
            int u = t.top + t.height - b.top;
            ball.move(0,2*u);   
            blop.play();
        }
        if ( intersects(ball,bottom) )
        {
            FloatRect bot= bottom.getGlobalBounds();
            FloatRect b= ball.getGlobalBounds();
            ballSpeed.y*= -1;
            int u = bot.top - b.height - b.top;
            ball.move(0,2*u); 
            blop.play();
        }
        // ball collides with player1 and player2
        if (intersects(ball,player1))
        {
            FloatRect p= player1.getGlobalBounds();
            FloatRect b= ball.getGlobalBounds();

            //let o be the center of p
            Vector2f o=Vector2f(p.left+p.width/2,p.top+p.height/2);
            //om: vector from o to the center of the ball
            Vector2f om=Vector2f(b.left+b.width/2-o.x,b.top+b.height/2-o.y);
            // let's scale om to square dimensions and act as if p is a square
            om.x/=p.width;
            om.y/=p.height;
            // reflect the ball according to the angle of om
            float angle=atan2(om.y,om.x);
            if ( abs(angle) <PI/2) //right - PI/4 is restrictive
            {
                ballSpeed.x= abs(ballSpeed.x);//in case of double contact
                ballSpeed.y= (b.top+b.height/2 - p.top - p.height/2) / 100;
                int u = p.left +  p.width - b.left;
                b.left = p.left +  p.width + u;
                ball.setPosition(b.left,b.top);
                //increase ball speed by 2%
                ballSpeed.x*=1.02f;
                ballSpeed.y*=1.02f;                
                blip.play();
            }
        }
        if ( intersects(ball,player2) )
        {
            FloatRect p=player2.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();

            //let o be the center of p
            Vector2f o=Vector2f(p.left+p.width/2,p.top+p.height/2);
            //om: vector from o to the center of the ball
            Vector2f om=Vector2f(b.left+b.width/2-o.x,b.top+b.height/2-o.y);
            // let's scale om to square dimensions and act as if p is a square
            om.x/=p.width;
            om.y/=p.height;
            // reflect the ball according to the angle of om
            float angle=atan2(om.y,om.x);
            if ( abs(angle) > PI/2) //left - 3*PI/4 is restrictive
            {
                ballSpeed.x= -abs(ballSpeed.x);//in case of double contact
                ballSpeed.y= (b.top+b.height/2 - p.top - p.height/2) / 100;
                int u = b.left + b.width - p.left;
                b.left = p.left -  b.width - u;
                ball.setPosition(b.left,b.top);
                //increase ball speed by 2%
                ballSpeed.x*=1.02f;
                ballSpeed.y*=1.02f;
                blip.play();
            }            
        }                        
        
        // check for scoring
        if (intersects(ball,left))
        {
            blam.play();
            p2Score++;
            std::stringstream str;
            str << p1Score << "   " << p2Score;
            score.setString(str.str());
            score.setPosition(width/2-score.getGlobalBounds().width/2,40);
            resetGame2();
        }
        if (intersects(ball,right))
        {
            blam.play();
            p1Score++;
            std::stringstream str;
            str << p1Score << "   " << p2Score;
            score.setString(str.str());
            score.setPosition(width/2-score.getGlobalBounds().width/2,40);
            resetGame1();
        }

    }

    void resetGame1()
    {
        FloatRect p=player1.getGlobalBounds();
        FloatRect b=ball.getGlobalBounds();
        ball.setPosition(p.left+p.width+5, height/2);
        ballSpeed.x=0.3f;
        ballSpeed.y=0.3f;
    }

    void resetGame2()
    {
        FloatRect p=player2.getGlobalBounds();
        FloatRect b=ball.getGlobalBounds();
        ball.setPosition(p.left-b.width-5, height/2);
        ballSpeed.x=-0.3f;
        ballSpeed.y=0.3f;
    }
};


int main()
{
    Game game;
    if (!game.init())
        return EXIT_FAILURE;
    return game.exec();
}

Result:

Our next game is break out. It follows logically from pong. It will stress out our new collision technique because bricks must reflect precisely the ball on each side.


Let’s make Break Out


In Break Out, the player controls a pad and uses it to reflect the ball on a grid of bricks. Bricks disappears if the ball hits them twice. The player wins the game if there are no more bricks. The player loses a live if the ball passes through the bottom of the screen. If he loses his 5 lives, he loses the game and must restart at level1.

We will use a a Brick class for single bricks and a Grid class to store all the bricks. Brick inherits from RectangleShape and Grid from FloatRect. The game is quite similar to pong except there is only one player and that the level is somehow dynamic. The configuration of the parameters is also sligthtly different, the pad moves faster but gives less angle to the ball.

T
he conditions of winning and losing the game depend on playerLives and are easy to handle.

The score is increased by 700 pts one the destructions of bricks. It requires a passing of arguments between Grid and Game.

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <math.h>
#include <algorithm>
#include <iostream>
#include <sstream>

#define PI   3.14159265358979323846

using namespace sf;

bool intersects (const RectangleShape & rect1,const RectangleShape & rect2)
{
    FloatRect r1=rect1.getGlobalBounds();
    FloatRect r2=rect2.getGlobalBounds();
    return r1.intersects (r2);
}

float clamp (const float x, const float a, const float b)
{
    return std::min(std::max(a,x),b);
}


class Brick : public RectangleShape
{
public:
    unsigned int armor;

    Brick():RectangleShape()
    {
        armor=2;
    }

    Brick(const Vector2f & size):RectangleShape(size)
    {
        armor=2;
    }


    bool collide(RectangleShape & ball, Vector2f & ballSpeed)
    {
        // ball collides brick
        if (intersects(ball,*this))
        {
            FloatRect r= getGlobalBounds();
            FloatRect b= ball.getGlobalBounds();

            //let o be the center of p
            Vector2f o=Vector2f(r.left+r.width/2,r.top+r.height/2);
            //om: vector from o to the center of the ball M
            Vector2f om=Vector2f(b.left+b.width/2-o.x,b.top+b.height/2-o.y);
            // let's scale om to square dimensions and act as if p is a square
            om.x/=r.width;
            om.y/=r.height;
            // reflect the ball according to the angle of om
            float angle=atan2(om.y,om.x);
            if ( angle >= PI/4 && angle <= 3*PI/4) //bottom
            {
                ballSpeed.y = abs(ballSpeed.y);                
                float u = r.top + r.height - b.top ;
                ball.move(0,2*u);
                return true;
            }
            if ( angle <= -PI/4 && angle >= -3*PI/4) //top
            {
                ballSpeed.y = -abs(ballSpeed.y);        
                float u = b.top + b.height - r.top;
                ball.move(0,-2*u);
                return true;
            }
            if ( angle <= -3*PI/4) //left
            {
                ballSpeed.y = -abs(ballSpeed.x);        
                float u = b.left + b.width - r.left;
                ball.move(-2*u,0);
                return true;
            }
            if ( abs(angle) <= PI/4) //right
            {
                ballSpeed.y = abs(ballSpeed.x);            
                float u = r.left + r.width - b.left;
                ball.move(2*u,0);
                return true;
            }            
        }
        return false;
    }


};


class Grid: public FloatRect
{
private:
    unsigned int rows;
    unsigned int columns;
    
    std::vector<Brick> * bricks;

public:
    Grid()
    {
        rows=0;
        columns=0;
        bricks = NULL;
    }
    
    ~Grid()
    {
        if (bricks != NULL)
            delete bricks;
    }

    void setDimensions(unsigned int col, unsigned int row)
    {
        rows=row;
        columns=col;
        if (bricks != NULL)
            delete bricks;

        bricks = new std::vector<Brick> (rows*columns);        
        init();
    }

    Vector2f cellSize()
    {
        return Vector2f(width/columns,height/rows);
    }

    void init()
    {
        for (int i=0;i<rows;i++)
            for (int j=0;j<columns;j++)
            {
                bricks->at(i+j*rows).setSize(cellSize());
                bricks->at(i+j*rows).setFillColor(Color::Blue);
                bricks->at(i+j*rows).setOutlineColor(Color::Black);
                bricks->at(i+j*rows).setOutlineThickness(3);
                bricks->at(i+j*rows).setPosition(left+j*cellSize().x, top+i*cellSize().y);
            }
    }

    void display (RenderWindow & window)
    {
        for (int i=0;i<rows;i++)
            for (int j=0;j<columns;j++)
            {
                if (bricks->at(i+j*rows).armor <=0)
                    continue;
                window.draw(bricks->at(i+j*rows));
            }
    }

    bool collide(RectangleShape & ball, Vector2f & ballSpeed, unsigned int & score)
    {
        for (int i=0;i<rows;i++)
            for (int j=0;j<columns;j++)
            {
                if (bricks->at(i+j*rows).armor <=0)
                    continue;
                if (bricks->at(i+j*rows).collide(ball, ballSpeed) )
                {
                    bricks->at(i+j*rows).armor--;
                    bricks->at(i+j*rows).setFillColor(Color::Green);
                    if (bricks->at(i+j*rows).armor==0)
                        score+=700;

                    return true;
                }
            }
        return false;
    }

    bool isGameWon()
    {
        for (int i=0;i<rows;i++)
            for (int j=0;j<columns;j++)
                if (bricks->at(i+j*rows).armor >0)
                    return false;
        return true;
    }

};

    

/**
 * Basic Game Structure
 * use init and exec in main
 */
class Game 
{
private:
    static const int FRAMES_PER_SECOND = 60;    
    static const int MAX_FRAMESKIP = 10; 
    static const int width = 640;
    static const int height= 480;
    static const int borderSize= 12;
    static const int margin = 50;
    static const int moveDistance = 40;
    

    RenderWindow window;
    Font font;

    RectangleShape top;
    RectangleShape left;
    RectangleShape right;
    RectangleShape bottom;    

    RectangleShape ball;

    Vector2f ballSpeed;

    RectangleShape player;    

    Text title;
    Text start;
    Text won;
    Text lost;
    Text score;
    Text lives;
    Text fps;

    SoundBuffer soundBuffer1;
    Sound blip;
    SoundBuffer soundBuffer2;
    Sound blam;    
    SoundBuffer soundBuffer3;
    Sound blap;
    SoundBuffer soundBuffer4;
    Sound blop;

    Grid grid;

    Time time;
    Int32 updateTime;

    unsigned int playerScore;
    unsigned int playerLives;

    enum states {INTRO, PLAYING, GAME_WON, GAME_LOST};
    
    int gameState;
    
public:
    Game()
    {
    }

    bool init()
    {
        VideoMode videoMode(width, height);
        window.create(videoMode,"Break Out");
        window.setVerticalSyncEnabled(true);
        window.setFramerateLimit(FRAMES_PER_SECOND);

        if (!font.loadFromFile("tomb.ttf"))
            return false;  

        if (!soundBuffer1.loadFromFile("blip.wav"))
            return false;
        
        if (!soundBuffer2.loadFromFile("blam.wav"))
            return false;
        
        if (!soundBuffer3.loadFromFile("blap.wav"))
            return false;

        if (!soundBuffer4.loadFromFile("blop.wav"))
            return false;

        setup();
        return true;
    }

    int exec()
    {
        Clock renderClock, updateClock;
        while (window.isOpen())
        {
            time=renderClock.getElapsedTime();
            float fFps=1000000/time.asMicroseconds();
            std::stringstream s;
            s<<"fps: "<<fFps;
            fps.setString(s.str());
            renderClock.restart();
        
            const Int64 frameTime = 1000000/FRAMES_PER_SECOND;
            Clock c;
            Time t=c.getElapsedTime();
            Int64 nextFrameTime=t.asMicroseconds()+frameTime;

            int loops = 0;
            while( t.asMicroseconds() < nextFrameTime && loops < MAX_FRAMESKIP) 
            {
                processEvents();
                updateTime=updateClock.restart().asMilliseconds();
                update();
                t=c.getElapsedTime();
                loops++;
            }        
            
            display();
        }
        
        return EXIT_SUCCESS;
    }

private:

    void processEvents()
    {
        Event event;
        while (window.pollEvent(event))
        {
            if ( (event.type == Event::Closed) ||
            ((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();    
            else
            if ((event.type == Event::KeyPressed) && (gameState == INTRO))
                gameState=PLAYING;
        }
    }

    void update()
    {
        if (gameState!=PLAYING)
            return;

        updatePlayer();
        checkCollisions();
        updateBall();

        // detect if game is over
        if (playerLives < 0)
            gameState=GAME_LOST;
    }

    void setup()
    {
        top.setPosition(0, 0);
        top.setSize(Vector2f(width, borderSize));

        left.setPosition(0, borderSize);
        left.setSize(Vector2f(borderSize, height+borderSize));

        right.setPosition(width-borderSize, borderSize);
        right.setSize(Vector2f(borderSize, height+borderSize));

        bottom.setPosition(0, height+2*borderSize);
        bottom.setSize(Vector2f(width, borderSize));
            
        top.setFillColor(Color(100,100,100));
        top.setOutlineColor(Color::Black);
        top.setOutlineThickness(2);

        left.setFillColor(Color(100,100,100));
        left.setOutlineColor(Color::Black);
        left.setOutlineThickness(2);

        right.setFillColor(Color(100,100,100));
        right.setOutlineColor(Color::Black);
        right.setOutlineThickness(2);

        bottom.setFillColor(Color(100,100,100));
        bottom.setOutlineColor(Color::Black);
        bottom.setOutlineThickness(2);
        
        ball.setPosition(width/2, height-margin);
        ball.setSize(Vector2f(20, 20));
        ball.setFillColor(Color::Green);
        ball.setOutlineColor(Color::Red);
        ball.setOutlineThickness(2);

        player.setSize(Vector2f(90,borderSize));
        player.setPosition(width/2-45, height-margin-borderSize);
        player.setFillColor(Color(0,122,245));
        player.setOutlineColor(Color::Green);
        player.setOutlineThickness(3);
        
        title.setString("Break Out!");
        title.setFont(font);
        title.setCharacterSize(50);
        title.setPosition(width/2-title.getGlobalBounds().width/2,100);
        title.setColor(Color::Blue); 
    
        start.setString("Press any key to start");
        start.setFont(font);
        start.setCharacterSize(30);
        start.setPosition(width/2-start.getGlobalBounds().width/2,400);
        start.setColor(Color::Red);

        won.setString("You have won this game.\n\n Congratulations !");
        won.setFont(font);
        won.setCharacterSize(20);
        won.setPosition(width/2-won.getGlobalBounds().width/2,height/2-won.getGlobalBounds().height/2);
        won.setColor(Color::Green);

        lost.setString("You have lost this game, \n better luck next time!");
        lost.setFont(font);
        lost.setCharacterSize(20);
        lost.setPosition(width/2-lost.getGlobalBounds().width/2,height/2-lost.getGlobalBounds().height/2);
        lost.setColor(Color::Red);

        score.setString("0");
        score.setFont(font);
        score.setCharacterSize(30);
        score.setPosition(width/2+score.getGlobalBounds().width/2,height-60);
        score.setColor(Color(0,0,100,50));

        lives.setString("5");
        lives.setFont(font);
        lives.setCharacterSize(50);
        lives.setPosition(width/2-lives.getGlobalBounds().width/2,height-60);
        lives.setColor(Color(0,0,100,50));

        fps.setString("0");
        fps.setFont(font);
        fps.setCharacterSize(30);
        fps.setPosition(fps.getGlobalBounds().width/2,height-40);
        fps.setColor(Color(52,0,100,50));
        
        blip=Sound(soundBuffer1);        
        blam=Sound(soundBuffer2);    
        blap=Sound(soundBuffer3);    
        blop=Sound(soundBuffer4);
        
        resetGame();
        gameState=INTRO;

        grid.setDimensions(7,5);
        grid.left=borderSize+5;
        grid.top=borderSize+5;
        grid.width=width-2*borderSize-10;
        grid.height=250;
        grid.init();
    }

    void display()
    {        
        window.clear(Color::White);
        switch(gameState)
        {
            case INTRO:
                window.draw(title);
                window.draw(start);
                break;
            case PLAYING:
                window.draw(left);
                window.draw(right);            
                grid.display(window);
                window.draw(player);    
                window.draw(ball);    
                window.draw(score);    
                window.draw(lives);    
                window.draw(top);
                window.draw(bottom);        
                window.draw(fps);    
                break;
            case GAME_WON:
                window.draw(won);
                break;
            case GAME_LOST:
                window.draw(lost);
                break;
        }
        window.display();
    }

    void updatePlayer()
    {
        // move player 1 pad
        if (Keyboard::isKeyPressed(Keyboard::Left))
        {
            player.move(-moveDistance*updateTime/50.0,0);
        }else
        if (Keyboard::isKeyPressed(Keyboard::Right))
        {
            player.move(moveDistance*updateTime/50.0,0);
        }
    }

    void updateBall()
    {
        ball.move(ballSpeed.x*updateTime,ballSpeed.y*updateTime);
    }

    void checkCollisions()
    {
                        
        // block players pad inside the play area
        if ( intersects(player,left) || intersects(player,right) )
        {
            FloatRect l=left.getGlobalBounds();
            FloatRect r=right.getGlobalBounds();
            Vector2f p=player.getPosition();
            p.x=clamp(p.x,l.left+l.width+5,r.left-player.getGlobalBounds().width-5);
            player.setPosition(p.x,p.y);
            blap.play();
        }
        // ball collides with top, left and right
        if (intersects(ball,top))
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ballSpeed.y= abs(ballSpeed.y);
            int u = t.top + t.height - b.top;
            ball.move(0,2*u);   
            blop.play();
        }
        if (intersects(ball,left))
        {
            FloatRect l=left.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ballSpeed.x= abs(ballSpeed.x);
            int u = l.left + l.width - b.left;
            b.left = l.left +  l.width + u;
            ball.setPosition(b.left,b.top);
            blop.play();
        }

        if (intersects(ball,right))
        {
            FloatRect r=right.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ballSpeed.x= -abs(ballSpeed.x);
            int u = b.left + b.width - r.left ;
            b.left = r.left -  b.width - u;
            ball.setPosition(b.left,b.top);
            blop.play();
        }

        if ( intersects(ball,bottom) )
        {            
            blam.play();            
            playerLives--;
            std::stringstream str;
            str << playerLives;
            lives.setString(str.str());
            lives.setPosition(width/2-lives.getGlobalBounds().width/2,height-60);
            resetBall();
        }
        // ball collides with player
        if (intersects(ball,player))
        {
            FloatRect p= player.getGlobalBounds();
            FloatRect b= ball.getGlobalBounds();

            //let o be the center of p
            Vector2f o=Vector2f(p.left+p.width/2,p.top+p.height/2);
            //om: vector from o to the center of the ball M
            Vector2f om=Vector2f(b.left+b.width/2-o.x,b.top+b.height/2-o.y);
            // let's scale om to square dimensions and act as if p is a square
            om.x/=p.width;
            om.y/=p.height;
            // reflect the ball according to the angle of om
            float angle=atan2(om.y,om.x);
            if ( angle < -PI/4 && angle > -3*PI/4) //top
            {
                ballSpeed.y = -abs(ballSpeed.y);//in case of double contact
                ballSpeed.x= (b.left+b.width/2 - p.left- p.width/2) / 200;
                int u = b.top + b.height - p.top;
                b.top = p.top - b.height - u;
                ball.setPosition(b.left,b.top);
                //increase ball speed by 2%
                ballSpeed.x*=1.02f;
                ballSpeed.y*=1.02f;                
                blip.play();
            }
        }    
        //ball collides with grid
        if (grid.collide(ball,ballSpeed,playerScore))
        {
            blap.play();
            playerScore+=200;
            std::stringstream str;
            str << playerScore << " pts  ";
            score.setString(str.str());
            score.setPosition(width/2+score.getGlobalBounds().width/2-margin,height-60);
        }
        // check for end of game
        if (grid.isGameWon())
            gameState=GAME_WON;
        if (playerLives<=0)
            gameState=GAME_LOST;
    }

    void resetGame()
    {
        playerScore=0;
        playerLives=5;
        resetBall();
    }
    void resetBall()
    {
        FloatRect p=player.getGlobalBounds();
        FloatRect b=ball.getGlobalBounds();
        ball.setPosition(p.left+p.width/2+5, p.top);
        ballSpeed.x=0.1f;
        ballSpeed.y=-0.3f;        
    }
};


int main()
{
    Game game;
    if (!game.init())
        return EXIT_FAILURE;
    return game.exec();
}

Result:

This is a simple implementation of Break Out! in which the bricks turn green after one hit and disappear after the second hit. The behaviour of the bricks is not completely clean yet.

SFML 2 – Tutorial – Pong

July 17, 2012 2 comments


Tutorial 02 – Basic Animations – Pong



In this tutorial, we will cover animations, collisions and the game of pong.




How to make animations ?



We can make objects on the screen look animated by updating their properties fast and smoothly.

For instance a movement animation will call setPosition or move to update the position of the object by small increments.

// if speed(vx,vy) is constant, the movement is linear and uniform
void updateShape(Shape &shape, int vx, int vy)
{
    shape.move(vx,vy);
}

A color animation would call setColor and a sprite animation would update its texture according to the current animation frame and type of movement.

How to detect collisions ?

The simplest collision test between two rectangles in motion is testing if the rectangles intersect.

Supposing our rectangles stay aligned to the axis (no rotation), we can use:

FloatRect RectangleShape::getGlobalBounds();

The FloatRect class provides an intersect function.

bool intersects (const RectangleShape & rect1,const RectangleShape & rect2)
{
    FloatRect r1=rect1.getGlobalBounds();
    FloatRect r2=rect2.getGlobalBounds();
    return r1.intersects (r2);
}

Once the collision is detected we can process it by updating the speed and position of the objects. In the following example, only the ball is moving, we’ll reflect its speed on collisions.

#include <SFML\Graphics.hpp>

using namespace sf;

bool intersects (const RectangleShape & rect1,const RectangleShape & rect2)
{
    FloatRect r1=rect1.getGlobalBounds();
    FloatRect r2=rect2.getGlobalBounds();
    return r1.intersects (r2);
}

int main()
{
    const int width = 640;
    const int height= 480;
    const int borderSize= 30;

    VideoMode videoMode(width, height);
    RenderWindow window(videoMode,"Rectangle Collision");

    RectangleShape top;
    RectangleShape left;
    RectangleShape right;
    RectangleShape bottom;

    top.setPosition(borderSize, 0);
    top.setSize(Vector2f(width-2*borderSize, borderSize));

    left.setPosition(0, 0);
    left.setSize(Vector2f(borderSize, height));

    right.setPosition(width-borderSize, 0);
    right.setSize(Vector2f(borderSize, height));

    bottom.setPosition(borderSize, height-borderSize);
    bottom.setSize(Vector2f(width-2*borderSize, borderSize));

    top.setFillColor(Color(100,100,100));
    top.setOutlineColor(Color::Blue);
    top.setOutlineThickness(3);

    left.setFillColor(Color(100,100,100));
    left.setOutlineColor(Color::Blue);
    left.setOutlineThickness(3);

    right.setFillColor(Color(100,100,100));
    right.setOutlineColor(Color::Blue);
    right.setOutlineThickness(3);

    bottom.setFillColor(Color(100,100,100));
    bottom.setOutlineColor(Color::Blue);
    bottom.setOutlineThickness(3);

    RectangleShape ball;
    ball.setPosition(width/2, height/2);
    ball.setSize(Vector2f(20, 20));
    ball.setFillColor(Color::Red);
    ball.setOutlineColor(Color::Yellow);
    ball.setOutlineThickness(1);

    Vector2 ballSpeed(0.1,0.1);

    while (window.isOpen())
    {
        window.clear(Color::White);
        window.draw(top);
        window.draw(bottom);
        window.draw(left);
        window.draw(right);
        window.draw(ball);
        window.display();

        Event event;
        while (window.pollEvent(event))
        {
            if ( (event.type == Event::Closed) ||
            ((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();

        }

        if (intersects(ball,top) || intersects(ball,bottom) )
        {
            ballSpeed.y=-ballSpeed.y;
        }
        if (intersects(ball,left) || intersects(ball,right))
        {
            ballSpeed.x=-ballSpeed.x;
        }

        ball.move(ballSpeed.x,ballSpeed.y);

    }
    return EXIT_SUCCESS;
}

Result:




How to make pong ?



In addition to the previous example, we’ll add two rectangles representing the two players on both sides of the screen. They will both reflect the ball. The left and right rectanles of the previous example will serve to detect who scores.

In order to get a little more realism on collisions, we will adjust the position of the ball after each collision, as described in the following figure.

If the ball touches the left or right edge of the screen, we update the score and reset the positions. The first player to reach the score of 11 with at least 2 points more than the other player wins the game.

#include <SFML/Graphics.hpp>
#include <algorithm>
#include <sstream>

using namespace sf;

bool intersects (const RectangleShape & rect1,const RectangleShape & rect2)
{
    FloatRect r1=rect1.getGlobalBounds();
    FloatRect r2=rect2.getGlobalBounds();
    return r1.intersects (r2);
}

int clamp (const int x, const int a, const int b)
{
    return std::min(std::max(a,x),b);
}

int main()
{
    const int width = 640;
    const int height= 480;
    const int borderSize= 12;
    const int margin = 50;
    const int moveDistance = 5;

    VideoMode videoMode(width, height);
    RenderWindow window(videoMode,"Pong SFML 2");

    Font font;
    if (!font.loadFromFile("tomb.ttf"))
        return EXIT_FAILURE;     
   
    RectangleShape top;
    RectangleShape left;
    RectangleShape right;
    RectangleShape bottom;    

    // setting up all items
    top.setPosition(0, 0);
    top.setSize(Vector2f(width, borderSize));

    left.setPosition(-borderSize, 0);
    left.setSize(Vector2f(borderSize, height));

    right.setPosition(width, 0);
    right.setSize(Vector2f(borderSize, height));

    bottom.setPosition(0, height-borderSize);
    bottom.setSize(Vector2f(width, borderSize));
            
    top.setFillColor(Color(100,100,100));
    top.setOutlineColor(Color::Blue);
    top.setOutlineThickness(3);

    left.setFillColor(Color(100,100,100));
    left.setOutlineColor(Color::Blue);
    left.setOutlineThickness(3);

    right.setFillColor(Color(100,100,100));
    right.setOutlineColor(Color::Blue);
    right.setOutlineThickness(3);

    bottom.setFillColor(Color(100,100,100));
    bottom.setOutlineColor(Color::Blue);
    bottom.setOutlineThickness(3);

    RectangleShape ball;
    ball.setPosition(width/2, height/2);
    ball.setSize(Vector2f(20, 20));
    ball.setFillColor(Color::Yellow);
    ball.setOutlineColor(Color::Red);
    ball.setOutlineThickness(2);

    Vector2<float> ballSpeed(0.1,0.1);

    RectangleShape player1;    
    player1.setSize(Vector2f(borderSize, 90));
    player1.setPosition(margin-borderSize, height/2-25);
    player1.setFillColor(Color(0,122,245));
    player1.setOutlineColor(Color::Red);
    player1.setOutlineThickness(3);

    RectangleShape player2;    
    player2.setSize(Vector2f(borderSize, 90));
    player2.setPosition(width-margin, height/2-25);
    player2.setFillColor(Color(0,122,245));
    player2.setOutlineColor(Color::Red);
    player2.setOutlineThickness(3);
    
    RectangleShape middleLine;
    middleLine.setFillColor(Color(100,100,100,30));
    middleLine.setOutlineColor(Color(0,0,100,30));
    middleLine.setOutlineThickness(2);
    middleLine.setPosition(width/2, 0);
    middleLine.setSize(Vector2f(0, height));

    Text title("Pong SFML 2",font,50);
    title.setPosition(width/2-title.getGlobalBounds().width/2,100);
    title.setColor(Color::Blue); 
    
    Text start("Press any key to start",font,30);
    start.setPosition(width/2-start.getGlobalBounds().width/2,400);
    start.setColor(Color::Red);

    Text won("You have won this game.\n\n Congratulations !",font,20);
    won.setPosition(width/2-won.getGlobalBounds().width/2,height/2-won.getGlobalBounds().height/2);
    won.setColor(Color::Green);

    Text lost("You have lost this game, \n better luck next time!",font,20);
    lost.setPosition(width/2-lost.getGlobalBounds().width/2,height/2-lost.getGlobalBounds().height/2);
    lost.setColor(Color::Red);

    Text score("0   0",font,50);
    score.setPosition(width/2-score.getGlobalBounds().width/2,40);
    score.setColor(Color(0,0,100,50));

    unsigned int p1Score=0, p2Score=0;

    enum states {INTRO, PLAYING, P1WON, P1LOST};
    
    int gameState=INTRO;

    while (window.isOpen())
    {
        window.clear(Color::White);
        switch(gameState)
        {
            case INTRO:
                window.draw(title);
                window.draw(start);
                break;
            case PLAYING:
                window.draw(middleLine);
                window.draw(left);
                window.draw(right);            
                window.draw(player1);    
                window.draw(player2);    
                window.draw(ball);    
                window.draw(score);    
                window.draw(top);
                window.draw(bottom);        
                break;
            case P1WON:
                window.draw(won);
                break;
            case P1LOST:
                window.draw(lost);
                break;
        }
        window.display();
        
        Event event;
        while (window.pollEvent(event))
        {
            if ( (event.type == Event::Closed) ||
            ((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();    
            else
            if ((event.type == Event::KeyPressed) && (gameState == INTRO))
                gameState=PLAYING;
        }
        
        if (gameState!=PLAYING)
            continue;

        // auto move player2 pad
        if (ball.getPosition().y < player2.getPosition().y)
            player2.move(0, -moveDistance/40.0);
        else if(ball.getPosition().y+ball.getSize().y > player2.getPosition().y+player2.getSize().y)
            player2.move(0, moveDistance/40.0);
        // move player 1 pad
        if (Keyboard::isKeyPressed(Keyboard::Up))
        {
            player1.move(0,-moveDistance/50.0);
        }else
        if (Keyboard::isKeyPressed(Keyboard::Down))
        {
            player1.move(0,moveDistance/50.0);
        }
        // block players pad inside the play area
        if ( intersects(player1,bottom) || intersects(player1,top) )
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=bottom.getGlobalBounds();
            Vector2f p=player1.getPosition();
            p.y=clamp(p.y,t.top+t.height+5,b.top-player1.getSize().y-5);
            player1.setPosition(p);
        }
        if ( intersects(player2,bottom) || intersects(player2,top) )
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=bottom.getGlobalBounds();
            Vector2f p=player2.getPosition();
            p.y=clamp(p.y,t.top+t.height+5,b.top-player2.getSize().y-5);
            player2.setPosition(p);
        }
        // ball collides with top and bottom
        if (intersects(ball,top))
        {
            FloatRect t=top.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ballSpeed.y=-ballSpeed.y;
            int u = t.top + t.height - b.top;
            ball.move(0,2*u);            
        }
        if ( intersects(ball,bottom) )
        {
            FloatRect bot= bottom.getGlobalBounds();
            FloatRect b= ball.getGlobalBounds();
            ballSpeed.y=-ballSpeed.y;
            int u = bot.top - b.height - b.top;
            ball.move(0,2*u);   
        }
        // ball collides with player1 and player2
        if (intersects(ball,player1))
        {
            FloatRect p= player1.getGlobalBounds();
            FloatRect b= ball.getGlobalBounds();
            ballSpeed.x= -ballSpeed.x;
            ballSpeed.y= (b.top+b.height/2 - p.top - p.height/2) / 100;
            int u = p.left +  p.width - b.left;
            b.left = p.left +  p.width + u;
            ball.setPosition(b.left,b.top);
            //increase ball speed by 1%
            ballSpeed.x=ballSpeed.x*1.01;
            ballSpeed.y=ballSpeed.y*1.01;
        }
        if ( intersects(ball,player2) )
        {
            FloatRect p=player2.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ballSpeed.x=-ballSpeed.x;
            ballSpeed.y= (b.top+b.height/2 - p.top - p.height/2) / 100;
            int u = b.left + b.width - p.left;
            b.left = p.left -  b.width - u;
            ball.setPosition(b.left,b.top);
            //increase ball speed by 1%
            ballSpeed.x=ballSpeed.x*1.01;
            ballSpeed.y=ballSpeed.y*1.01;
        }                        
        
        // check for scoring
        if (intersects(ball,left))
        {
            p2Score++;
            std::stringstream str;
            str << p1Score << "   " << p2Score;
            score.setString(str.str());
            score.setPosition(width/2-score.getGlobalBounds().width/2,40);
            FloatRect p=player2.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ball.setPosition(p.left-b.width-5, height/2);
            ballSpeed.x=-0.1;
            ballSpeed.y=0.1;

        }
        if (intersects(ball,right))
        {
            p1Score++;
            std::stringstream str;
            str << p1Score << "   " << p2Score;
            score.setString(str.str());
            score.setPosition(width/2-score.getGlobalBounds().width/2,40);
            FloatRect p=player1.getGlobalBounds();
            FloatRect b=ball.getGlobalBounds();
            ball.setPosition(p.left+p.width+5, height/2);
            ballSpeed.x=0.1;
            ballSpeed.y=0.1;
        }
        // detect if game is over
        if (p1Score >=11 && p1Score >= p2Score +2)
            gameState=P1WON;
        if (p2Score >=11 && p2Score >= p1Score +2)
            gameState=P1LOST;

        
        
        ball.move(ballSpeed.x,ballSpeed.y);

        
    }
    return EXIT_SUCCESS;
}

Result:

Sociality And Individuality – GLS24

July 17, 2012 Comments off

IT University of Copenhagen – June, 2012

A talk from the Game Lecture Series at the IT University of Copenhagen by Richard Evans.

Richard Evans describes the social practice and how it creates a dynamic with individualism.

Players Imbuing Meaning – GLS23

July 16, 2012 Comments off

IT University of Copenhagen – June, 2012

A talk from the Game Lecture Series by Mirjam Palosaari Eladhari.

The title of the talk is: ‘Players Imbuing Meaning: Co-Creation Of Challenges In A Prototype MMO’.

SFML 2 – Tutorial – Introduction

July 15, 2012 1 comment


Tutorial 01 – Introduction – Plus or Minus



SFML is a cross-platform API. It supports Windows, Linux and Mac. Its license makes it free even for commercial use. It was created by the french programmer Laurent Gomila in C++ and there are bindings for various languages. SFML 2 is available on www.sfml-dev.org.

In this tutorial we will learn its basics through simple examples. I will be developing on Windows using Visual C++ 10 but it should be applicable to other configurations.

SDML Overview:

SFML is made of 5 modules: Graphics, Audio, Network, Window and System.

Each module provides its own header, lib and dll which must respectively be included, linked and present in the path to be used.

In this tutorial, we will see how to display windows, render text, respond to events and make a simple game using SFML2.


So, how to display a basic window ?



First, we’ll create a window then we’ll display it, it’s as simple as it should be.

All SFML classes and structures are in the namespace sf. You can either use namespace sf or use the sf:: prefix to avoid namespace conflicts.

The Window class is obviously in the Window module. We’ll use it by including its header, SFML\Window.hpp, and linking its library, sfml-window.lib.

There are multiple ways to tell the compiler to link SFML libs. For instance with Visual C++ you can use the following code or edit the project settings.

#ifdef _MSC_VER
#    ifdef _WIN32
#        ifndef _DEBUG
#            pragma comment( lib, "sfml-window.lib" )
#        else
#            pragma comment( lib, "sfml-window-d.lib" )
#        endif
#    endif
#endif

The default Window constructor doesn’t actually create the window, so we’ll use:

Window (VideoMode mode, const std::string & title)

We need to create a VideoMode that will set the resolution of our window. The default VideoMode constructor sets the resolution to 0×0, so we’ll use:

VideoMode (unsigned int modeWidth, unsigned int modeHeight)

Then we can display the window with its display() function. Please note that the constructor we used
will already call a display of the window frame. The display function draws the inside. Afterwards we close it with close(). In order to actually see it we’ll call a system(“PAUSE”) before closing it.

#include <SFML/Window.hpp>

using namespace sf;

int main()
{
    VideoMode videoMode(320,240);
    Window window(videoMode,"Basic Display Window");
    window.display();
    system("PAUSE");
    window.close();
    return EXIT_SUCCESS;
}

Result:

A black empty window is displayed on the screen in addition to the console, it doesn’t respond to any events so there is no way to give it focus.

We can minimize all windows that are in front to see it better but that leaves an image on our window cause it doesn’t even refresh the display.


How to refresh the display ?



We’ll just call it again and again in an infinite loop.

#include <SFML\Window.hpp>

using namespace sf;

int main()
{
    VideoMode videoMode(320,240);
    Window window(videoMode,"Basic Infinite Loop Display");

    while (true)
    {
        window.display();
    }
    return EXIT_SUCCESS;
}

Result:

It refreshes the display correctly, after a another window passes in front of this window, it goes back to black.

It still doesn’t respond to events and it never returns so it must be killed after execution.


How to make the window respond to event ?



First we declare an Event called event. The window.pollEvent(event) function sets event to the first event on top of the event stack, removes it from the stack and returns true. If the event stack is empty it returns false.

We will process all events of the stack using:

while (window.pollEvent(event))

We can then perform any action required according to the data contained in event. For instance, if event.type is of value Event::Closed, it means that the window has received a close request. We’ll also get rid of that horrible infinite loop by looping only if the window is open.

#include <SFML\Window.hpp>

using namespace sf;

int main()
{
	VideoMode videoMode(320,240);
	Window window(videoMode,"Responsive Window");

	while (window.isOpen())
    {
        window.display();

        Event event;
		while (window.pollEvent(event))
        {
			if (event.type == Event::Closed)
                window.close();
        }

    }

    return EXIT_SUCCESS;
}

Result:

The window can be closed using the close button on the title bar of the window. It can also be minimized, maximized or resized without additional code.


How to also use the key esc to quit ?



In the same way that event.type can equal Event::Closed it can also be

Event::KeyPressed.

In that case event is of type KeyEvent and event.key.code has a different value
for each key. If it is equal to Keyboard::Escape then we’ll close the window.

#include <SFML\Window.hpp>

using namespace sf;

int main()
{
	VideoMode videoMode(320,240);
	Window window(videoMode,"KeyEvent Window");

	while (window.isOpen())
    {
        window.display();

        Event event;
		while (window.pollEvent(event))
        {
			if (event.type == Event::Closed)
                window.close();
			else
			if (event.type == Event::KeyPressed)
			{
				if (event.key.code==Keyboard::Escape)
					window.close();
			}
        }
    }
    return EXIT_SUCCESS;
}

Result:

As expected, the escape key closes the window.

Note that the condition to close the window can be united in only one if as the expression:

if ( (event.type == Event::Closed) ||
((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )


How to draw a rectangle ?



There are 3 classes of Shape that are drawable in SFML: RectangleShape, CircleShape and ConvexShape.

They are in the Graphics module and they require the use of a RenderWindow. This class inherits
from Window and also from RenderTarget which provides it with a clear function to quickly erase the screen.

We”ll create a RectangleShape, the default constructor sets its size to 0 x 0. So we’ll use setSize to configure its size, setPosition for its position, setFillColor and setOutlineColor to set its colors. We’ll use setOutlineThickness to adjust the border width. And we’ll update its position with arrow keys using the move function which calls setPosition itself. Of course, we need to call window.draw(rectangle) and clear the screen between two display calls.

#include <SFML\Graphics.hpp>

using namespace sf;

int main()
{
	VideoMode videoMode(320,240);
	RenderWindow window(videoMode,"KeyEvent Window");

	RectangleShape rectangle;
	rectangle.setPosition(30, 30);
	rectangle.setSize(Vector2f(50, 30));

	rectangle.setFillColor(Color::Yellow);
	rectangle.setOutlineColor(Color::Blue);
	rectangle.setOutlineThickness(3);

	while (window.isOpen())
    {
		window.clear();
		window.draw(rectangle);
        window.display();

        Event event;
		while (window.pollEvent(event))
        {
			if ( (event.type == Event::Closed) ||
			((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();
			else
			{
				if (event.type == Event::KeyPressed)
				{
					switch(event.key.code)
					{
						case Keyboard::Up: rectangle.move(0,-10);
						break;
						case Keyboard::Down: rectangle.move(0,10);
						break;
						case Keyboard::Left: rectangle.move(-10,0);
						break;
						case Keyboard::Right: rectangle.move(10,0);
						break;
					}

				}
			}

        }
    }
    return EXIT_SUCCESS;
}

Result:




How to display text ?



Text is a class of the Graphics module. It contains a String (from the System module), a Color, a Font and a bounding rectangle. So we’ll also use the System module.

Text text(“Hello”) creates the object and window.draw(text) displays it.

#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>

using namespace sf;

int main()
{
	VideoMode videoMode(320,240);
	RenderWindow window(videoMode,"Hello Text");

	Text text("Hello \nSFML 2 \nWorld!");
	text.setPosition(20,10);
	//text.setColor(Color::Red);

	while (window.isOpen())
    {
		window.clear();
		window.draw(text);
		window.display();

        Event event;
		while (window.pollEvent(event))
        {
			if ( (event.type == Event::Closed) ||
			((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();

        }
    }
    return EXIT_SUCCESS;
}

Result:


How to use fonts ?



Open a font file:

Font::loadFromFile (const std::string & filename)

There are about 10 supported font file formats (notably ttf and fnt).

Create the text using the font with a pointSize of 20.

Text text(“Hello \nSFML 2 \nWorld!”,font,20)

You’ll need to have the font file on your disk at run time, here “tomb.ttf”.

#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>

using namespace sf;

int main()
{
	VideoMode videoMode(320,240);
	RenderWindow window(videoMode,"Hello Text");

	Font font;
	if (!font.loadFromFile("tomb.ttf"))
		return EXIT_FAILURE;

	Text text("Hello \nSFML 2 \nWorld!",font,20);
 	text.setPosition(20,10);
	text.setColor(Color::Red);

	while (window.isOpen())
    {
		window.clear();
		window.draw(text);
		window.display();

        Event event;
		while (window.pollEvent(event))
        {
			if ( (event.type == Event::Closed) ||
			((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();

        }
    }
    return EXIT_SUCCESS;
}

Result:



How to make the game of plus or minus ?



This may be one of the simplest game ever.

Player 1 picks a number x between 0 and 100.
Player 2 tries to guess x by proposing a number y
Player 1 tells player 2 if y is inferior, equal or superior to x.
Player 2 wins if he guesses x correctly and loses if he needs more than 10 tries.

Here the computer will play player 1 and we will try out the role of player 2 while play testing this game.

We will use the rand function of stdlib. It must be initialised (seeded) with srand. The same seed will generate the same sequence of random numbers so we will use time(NULL) as seed.

srand(static_cast (time()));

We need to capture the text entered by the user with TextEvent. If its a number we append it to textEntered, if it’s return, we process it and we ignore other characters.

To process it, we convert textEntered to an int and we compare it with the secret number, then we set textEntered to an empty string, awaiting the next try.

#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <ctime>
#include <sstream>

using namespace sf;
using namespace std;

int main()
{
    VideoMode videoMode(320,240);
    RenderWindow window(videoMode,"Plus Or Minus");

    Font font;
    if (!font.loadFromFile("tomb.ttf"))
        return EXIT_FAILURE;     

    Text title("Plus Or Minus",font,20);
    title.setPosition(10,10);
    title.setColor(Color::Blue);

    Text question("What is your guess ?",font,20);
    question.setPosition(5,30);
    question.setColor(Color::White);

    Text enter("",font,20);
    enter.setPosition(5,50);
    enter.setColor(Color::Green);
    String textEntered;
    int numEntered=0;

    Text status("",font,16);
    status.setPosition(100,50);
    status.setColor(Color::Yellow);
    stringstream out("");

    Text won("You have discovered\n the secret number\n in less than 10 tries,\n well played !",font,20);
    won.setPosition(5,70);
    won.setColor(Color::Green);

    Text lost("You have failed to\n discover the secret\n number in less than\n 10 tries, better \nluck next time!",font,20);
    lost.setPosition(5,70);
    lost.setColor(Color::Red);

    srand(static_cast<unsigned int> (time(NULL)));

    int secretNumber = rand() % 100;

    bool isGameOn=true;
    bool playerWon=false;
    int tryNumber=0;

    while (window.isOpen())
    {        
        window.clear();
        window.draw(title);
        if (isGameOn)
        {
            window.draw(question);
            window.draw(enter);
            window.draw(status);
        }
        else
        {
            if (playerWon)
                window.draw(won);
            else
                window.draw(lost);                    
        }

        window.display();

        Event event;
        while (window.pollEvent(event))
        {
            if ( (event.type == Event::Closed) ||
                ((event.type == Event::KeyPressed) && (event.key.code==Keyboard::Escape)) )
                window.close();            

            if (event.type == Event::TextEntered)
            {
                char c=static_cast<char> (event.text.unicode);
                if (c >= '0' && c <= '9' )
                {
                    textEntered += event.text.unicode;
                    enter.setString (textEntered); //update the enter Text
                }
                else if (c == '\r') //'\b' to handle backspace
                {
                    tryNumber++;

                    stringstream in(enter.getString());
                    in >> numEntered; //just get str as an int                    


                    if (numEntered>secretNumber)
                    {                        
                        out << tryNumber;
                        out << ". ";
                        out << numEntered;
                        out <<": Minus" << endl;                        
                        status.setString(out.str());
                    }
                    if (numEntered<secretNumber)
                    {        
                        out << tryNumber;
                        out << ". ";
                        out << numEntered;
                        out <<": Plus" << endl;            
                        status.setString(out.str()); 
                    }
                    
                    if (numEntered==secretNumber)
                    {
                        playerWon=true;        
                        status.setString("");
                        isGameOn=false;
                    }
                    if (tryNumber>10)
                    {
                        playerWon=false;
                        status.setString("");
                        isGameOn=false;
                    }
                    textEntered =""; //reset textEntered
                }
            }
        }

    }
    return EXIT_SUCCESS;
}

Result:

The next tutorial will cover animations, collisions and pong.

Why People Still Matter – GLS22

July 15, 2012 Comments off

IT University of Copenhagen – May, 2012

A talk by Jonathan Gratch: “Why people still matter: Modeling human behaviour processes in agents.”

What Do Games Represent ? – GLS21

July 14, 2012 Comments off

IT University of Copenhagen – May, 2012

Stephan Günzel explores the relationship of space with video games in “What Do Games Represent? Space And Videogames”. His ideas are examplified with Tetris, Zork, Portal or Mirror Edge.

Follow

Get every new post delivered to your Inbox.